feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
.line {
|
||||
height: 1px;
|
||||
margin-top: -3px;
|
||||
margin-bottom: 14px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.chat-history-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { type FC } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ViewVariableType, useNodeTestId } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Switch, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
|
||||
import { type ComponentProps } from '@/nodes-v2/components/types';
|
||||
import {
|
||||
OutputTree,
|
||||
type OutputTreeProps,
|
||||
} from '@/form-extensions/components/output-tree';
|
||||
import { FormCard } from '@/form-extensions/components/form-card';
|
||||
import { ChatHistoryRound } from '@/components/chat-history-round';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const VALUE = [
|
||||
{
|
||||
key: nanoid(),
|
||||
name: 'chatHistory',
|
||||
type: ViewVariableType.ArrayObject,
|
||||
children: [
|
||||
{
|
||||
key: nanoid(),
|
||||
name: 'role',
|
||||
type: ViewVariableType.String,
|
||||
},
|
||||
{
|
||||
key: nanoid(),
|
||||
name: 'content',
|
||||
type: ViewVariableType.String,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as OutputTreeProps['value'];
|
||||
|
||||
export interface ChatHistoryValue {
|
||||
enableChatHistory: boolean;
|
||||
chatHistoryRound: number;
|
||||
}
|
||||
|
||||
export const ChatHistory: FC<
|
||||
ComponentProps<ChatHistoryValue> & {
|
||||
style: React.CSSProperties;
|
||||
showLine?: boolean;
|
||||
}
|
||||
> = ({ value, onChange, name, style, showLine = true }) => {
|
||||
const { getNodeSetterId } = useNodeTestId();
|
||||
const readonly = useReadonly();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormCard.Action>
|
||||
<Tooltip content={I18n.t('wf_chatflow_125')} position="right">
|
||||
<div className="flex items-center gap-1" style={style}>
|
||||
<div className={styles['chat-history-text']}>
|
||||
{I18n.t('wf_chatflow_124')}
|
||||
</div>
|
||||
<Switch
|
||||
size="mini"
|
||||
checked={value.enableChatHistory}
|
||||
data-testid={getNodeSetterId(name)}
|
||||
onChange={checked => {
|
||||
if (value.enableChatHistory === checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange?.({
|
||||
...value,
|
||||
enableChatHistory: checked,
|
||||
});
|
||||
}}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormCard.Action>
|
||||
{value.enableChatHistory ? (
|
||||
<div className="relative">
|
||||
<OutputTree
|
||||
id="chat-history"
|
||||
readonly
|
||||
value={VALUE}
|
||||
defaultCollapse
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onChange={() => {}}
|
||||
withDescription={false}
|
||||
withRequired={false}
|
||||
noCard
|
||||
/>
|
||||
{showLine ? <div className={styles.line} /> : null}
|
||||
|
||||
<ChatHistoryRound
|
||||
value={value.chatHistoryRound}
|
||||
readonly={readonly}
|
||||
onChange={w => {
|
||||
onChange({
|
||||
...value,
|
||||
chatHistoryRound: Number(w),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const chatHistory = {
|
||||
key: 'ChatHistory',
|
||||
component: ChatHistory,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const REASONING_CONTENT_NAME = 'reasoning_content';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { provideReasoningContentEffect } from './provide-reasoning-content';
|
||||
export {
|
||||
formatReasoningContentOnInit,
|
||||
formatReasoningContentOnSubmit,
|
||||
sortOutputs,
|
||||
isSystemReasoningContent,
|
||||
omitSystemReasoningContent,
|
||||
} from './utils';
|
||||
|
||||
export { REASONING_CONTENT_NAME } from './constants';
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
type Effect,
|
||||
DataEvent,
|
||||
FlowNodeFormData,
|
||||
type FormModelV2,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type ViewVariableTreeNode } from '@coze-workflow/base';
|
||||
|
||||
import { WorkflowModelsService } from '@/services';
|
||||
|
||||
import { getOutputs } from './utils';
|
||||
|
||||
function createEffect(): Effect {
|
||||
return ({ value, context: { node } }) => {
|
||||
const modelType = value?.modelType;
|
||||
|
||||
const form = node
|
||||
?.getData(FlowNodeFormData)
|
||||
?.getFormModel<FormModelV2>()?.nativeFormModel;
|
||||
|
||||
if (!form || !modelType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputs = form.getValueIn<ViewVariableTreeNode[] | undefined>(
|
||||
'outputs',
|
||||
);
|
||||
|
||||
const isBatch = form.getValueIn('batchMode') === 'batch';
|
||||
const modelsService = node.getService(WorkflowModelsService);
|
||||
|
||||
form.setValueIn(
|
||||
'outputs',
|
||||
getOutputs({ modelType, outputs, isBatch, modelsService }),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const provideReasoningContentEffect = [
|
||||
{
|
||||
effect: createEffect(),
|
||||
event: DataEvent.onValueChange,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { nanoid } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
ViewVariableType,
|
||||
type ViewVariableTreeNode,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { type WorkflowModelsService } from '@/services';
|
||||
|
||||
import { REASONING_CONTENT_NAME } from './constants';
|
||||
|
||||
const readonlyTooltip = I18n.t(
|
||||
'workflow_250217_02',
|
||||
undefined,
|
||||
'推理内容,支持输出思维链的模型特有',
|
||||
);
|
||||
|
||||
interface ViewVariableTreeNodeWithReadonly extends ViewVariableTreeNode {
|
||||
readonly?: boolean;
|
||||
readonlyTooltip?: string;
|
||||
}
|
||||
|
||||
const generateReasoningContent = () => ({
|
||||
key: nanoid(),
|
||||
name: REASONING_CONTENT_NAME,
|
||||
type: ViewVariableType.String,
|
||||
readonly: true,
|
||||
readonlyTooltip,
|
||||
});
|
||||
|
||||
const isReasoningContent = (node: ViewVariableTreeNodeWithReadonly) =>
|
||||
node.name === REASONING_CONTENT_NAME;
|
||||
|
||||
export const isSystemReasoningContent = (
|
||||
node: ViewVariableTreeNodeWithReadonly,
|
||||
) => !!(isReasoningContent(node) && node.readonly);
|
||||
|
||||
const excludeReasoningContent = (nodes?: ViewVariableTreeNode[]) =>
|
||||
(nodes ?? []).filter(node => !isSystemReasoningContent(node));
|
||||
|
||||
const includeReasoningContent = (nodes?: ViewVariableTreeNode[]) =>
|
||||
(nodes ?? []).filter(node => isSystemReasoningContent(node));
|
||||
|
||||
const addReasoningContent = (
|
||||
value?: ViewVariableTreeNode[],
|
||||
isBatch?: boolean,
|
||||
) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isBatch) {
|
||||
return [
|
||||
{
|
||||
...value[0],
|
||||
children: [
|
||||
...excludeReasoningContent(value[0]?.children),
|
||||
generateReasoningContent(),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [...excludeReasoningContent(value), generateReasoningContent()];
|
||||
};
|
||||
|
||||
const removeReasoningContent = (
|
||||
value?: ViewVariableTreeNode[],
|
||||
isBatch?: boolean,
|
||||
) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (isBatch) {
|
||||
const [one, ...rest] = value;
|
||||
return [
|
||||
{
|
||||
...one,
|
||||
children: excludeReasoningContent(one?.children),
|
||||
},
|
||||
...rest,
|
||||
];
|
||||
} else {
|
||||
return [...excludeReasoningContent(value)];
|
||||
}
|
||||
};
|
||||
|
||||
function findReasoningContent(
|
||||
outputs: ViewVariableTreeNodeWithReadonly[] | undefined,
|
||||
isBatch: boolean,
|
||||
fn: (node) => boolean,
|
||||
): ViewVariableTreeNodeWithReadonly | undefined {
|
||||
if (!outputs) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isBatch) {
|
||||
return outputs[0]?.children?.find(node => fn(node));
|
||||
}
|
||||
return outputs.find(node => fn(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* output 属性排序,保证 reasoning content 在最下面
|
||||
*/
|
||||
export const sortOutputs = (
|
||||
value: ViewVariableTreeNode[] | undefined,
|
||||
isBatch?: boolean,
|
||||
) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isBatch) {
|
||||
const [one, ...rest] = value;
|
||||
return [
|
||||
{
|
||||
...one,
|
||||
children: [
|
||||
...excludeReasoningContent(one?.children),
|
||||
...includeReasoningContent(one?.children),
|
||||
],
|
||||
},
|
||||
...rest,
|
||||
];
|
||||
}
|
||||
return [...excludeReasoningContent(value), ...includeReasoningContent(value)];
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据模型类型获取输出
|
||||
* @param modelType
|
||||
* @param outputs
|
||||
* @param isBatch
|
||||
* @returns
|
||||
*/
|
||||
export function getOutputs({
|
||||
modelType,
|
||||
outputs,
|
||||
isBatch,
|
||||
modelsService,
|
||||
}: {
|
||||
modelType: number | undefined;
|
||||
outputs: ViewVariableTreeNode[] | undefined;
|
||||
isBatch: boolean;
|
||||
modelsService: WorkflowModelsService;
|
||||
}) {
|
||||
if (!modelType) {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
if (modelsService.isCoTModel(modelType)) {
|
||||
outputs = addReasoningContent(outputs, isBatch);
|
||||
} else {
|
||||
outputs = removeReasoningContent(outputs, isBatch);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化时格式化推理内容为只读
|
||||
* @param outputs
|
||||
* @param isBatch
|
||||
* @returns
|
||||
*/
|
||||
export function formatReasoningContentOnInit({
|
||||
outputs,
|
||||
isBatch,
|
||||
modelType,
|
||||
modelsService,
|
||||
}: {
|
||||
outputs: ViewVariableTreeNode[] | undefined;
|
||||
isBatch: boolean;
|
||||
modelType?: number;
|
||||
modelsService: WorkflowModelsService;
|
||||
}) {
|
||||
if (!outputs) {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
let newOutputs: ViewVariableTreeNode[] | undefined = outputs;
|
||||
if (modelType && modelsService.isCoTModel(modelType)) {
|
||||
// 后端返回的没有readonly字段,需要前端处理, 取第一个类型是string的reasoning_content
|
||||
const reasoningContent = findReasoningContent(
|
||||
outputs,
|
||||
isBatch,
|
||||
item => isReasoningContent(item) && item.type === ViewVariableType.String,
|
||||
);
|
||||
if (reasoningContent) {
|
||||
reasoningContent.readonly = true;
|
||||
reasoningContent.readonlyTooltip = readonlyTooltip;
|
||||
} else {
|
||||
// 存量数据兼容,如果是推理模型,则添加推理内容字段
|
||||
newOutputs = addReasoningContent(outputs, isBatch);
|
||||
}
|
||||
}
|
||||
|
||||
return newOutputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交时格式化推理内容移除readonly
|
||||
* @param outputs
|
||||
* @param isBatch
|
||||
* @returns
|
||||
*/
|
||||
export function formatReasoningContentOnSubmit(
|
||||
outputs: ViewVariableTreeNodeWithReadonly[] | undefined,
|
||||
isBatch: boolean,
|
||||
) {
|
||||
if (!outputs) {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
const reasoningContent = findReasoningContent(
|
||||
outputs,
|
||||
isBatch,
|
||||
isSystemReasoningContent,
|
||||
);
|
||||
if (reasoningContent?.readonly) {
|
||||
delete reasoningContent.readonly;
|
||||
}
|
||||
|
||||
if (reasoningContent?.readonlyTooltip) {
|
||||
delete reasoningContent.readonlyTooltip;
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除 outputs 中的 为readonly的 reasoning_content
|
||||
*/
|
||||
export const omitSystemReasoningContent = (
|
||||
value: ViewVariableTreeNodeWithReadonly[] | undefined,
|
||||
isBatch?: boolean,
|
||||
) => {
|
||||
// 批量,去除 children 中的 为readonly的 reasoning_content
|
||||
if (isBatch) {
|
||||
return value?.map(v => ({
|
||||
...v,
|
||||
children: v?.children?.filter(c => !isSystemReasoningContent(c)),
|
||||
}));
|
||||
}
|
||||
// 单次,去除 value 中的 为readonly的 reasoning_content
|
||||
return value?.filter(v => !isSystemReasoningContent(v));
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useForm } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
/**
|
||||
* 获取模型type
|
||||
*/
|
||||
export function useModelType() {
|
||||
const form = useForm();
|
||||
|
||||
const modelType = form.getValueIn('model')?.modelType;
|
||||
return modelType;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.input-add-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.columns-title{
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { LLM_NODE_REGISTRY } from './llm-node-registry';
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { get, set, omit, isEmpty } from 'lodash-es';
|
||||
import {
|
||||
Field,
|
||||
ValidateTrigger,
|
||||
FieldArray,
|
||||
type FieldRenderProps,
|
||||
type FieldArrayRenderProps,
|
||||
type FormMetaV2,
|
||||
type FormRenderProps,
|
||||
nanoid,
|
||||
type Validate,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { PublicScopeProvider } from '@coze-workflow/variable';
|
||||
import { nodeUtils, ViewVariableType } from '@coze-workflow/nodes';
|
||||
import {
|
||||
BlockInput,
|
||||
concatTestId,
|
||||
type InputValueDTO,
|
||||
type RefExpression,
|
||||
type ValueExpression,
|
||||
ValueExpressionType,
|
||||
} from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozPlus, IconCozMinus } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
|
||||
import { type IModelValue } from '@/typing';
|
||||
import { WorkflowModelsService } from '@/services';
|
||||
import { provideNodeOutputVariablesEffect } from '@/nodes-v2/materials/provide-node-output-variables';
|
||||
import { createProvideNodeBatchVariables } from '@/nodes-v2/materials/provide-node-batch-variable';
|
||||
import { fireNodeTitleChange } from '@/nodes-v2/materials/fire-node-title-change';
|
||||
import { createValueExpressionInputValidate } from '@/nodes-v2/materials/create-value-expression-input-validate';
|
||||
import { ChatHistory } from '@/nodes-v2/llm/chat-history';
|
||||
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
|
||||
import { ValueExpressionInput } from '@/nodes-v2/components/value-expression-input';
|
||||
import { Outputs } from '@/nodes-v2/components/outputs';
|
||||
import { createNodeInputNameValidate } from '@/nodes-v2/components/node-input-name/validate';
|
||||
import { NodeInputName } from '@/nodes-v2/components/node-input-name';
|
||||
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
|
||||
import { BatchMode } from '@/nodes-v2/components/batch-mode';
|
||||
import { Batch } from '@/nodes-v2/components/batch/batch';
|
||||
import { useGetWorkflowMode, useGlobalState } from '@/hooks';
|
||||
import { FormCard } from '@/form-extensions/components/form-card';
|
||||
import { ColumnsTitleWithAction } from '@/form-extensions/components/columns-title-with-action';
|
||||
import { ModelSelect } from '@/components/model-select';
|
||||
|
||||
import { nodeMetaValidate } from '../materials/node-meta-validate';
|
||||
import { SettingOnError } from '../components/setting-on-error';
|
||||
import NodeMeta from '../components/node-meta';
|
||||
import { Vision, isVisionInput } from './vision';
|
||||
import {
|
||||
llmOutputTreeMetaValidator,
|
||||
llmInputNameValidator,
|
||||
} from './validators';
|
||||
import {
|
||||
getDefaultLLMParams,
|
||||
modelItemToBlockInput,
|
||||
reviseLLMParamPair,
|
||||
} from './utils';
|
||||
import { UserPrompt } from './user-prompt';
|
||||
import { type FormData } from './types';
|
||||
import { SystemPrompt } from './system-prompt';
|
||||
import { type BoundSkills } from './skills/types';
|
||||
import {
|
||||
formatFcParamOnInit,
|
||||
formatFcParamOnSubmit,
|
||||
} from './skills/data-transformer';
|
||||
import { Skills } from './skills';
|
||||
import {
|
||||
formatReasoningContentOnInit,
|
||||
formatReasoningContentOnSubmit,
|
||||
provideReasoningContentEffect,
|
||||
sortOutputs,
|
||||
} from './cot';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
/** 默认会话轮数 */
|
||||
const DEFAULT_CHAT_ROUND = 3;
|
||||
|
||||
const Render = ({ form }: FormRenderProps<FormData>) => {
|
||||
const readonly = useReadonly();
|
||||
const { isChatflow } = useGetWorkflowMode();
|
||||
const { isBindDouyin } = useGlobalState();
|
||||
|
||||
return (
|
||||
<PublicScopeProvider>
|
||||
<>
|
||||
<NodeMeta
|
||||
deps={['outputs', 'batchMode']}
|
||||
outputsPath={'outputs'}
|
||||
batchModePath={'batchMode'}
|
||||
/>
|
||||
<Field name={'batchMode'}>
|
||||
{({ field }: FieldRenderProps<string>) => (
|
||||
<BatchMode
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field name={'model'}>
|
||||
{({ field }: FieldRenderProps<IModelValue | undefined>) => (
|
||||
<FormCard
|
||||
header={I18n.t('workflow_detail_llm_model')}
|
||||
tooltip={I18n.t('workflow_detail_llm_prompt_tooltip')}
|
||||
>
|
||||
<ModelSelect {...field} readonly={readonly} />
|
||||
</FormCard>
|
||||
)}
|
||||
</Field>
|
||||
<Batch batchModeName={'batchMode'} name={'batch'} />
|
||||
{!isBindDouyin ? (
|
||||
<Field name="fcParam">
|
||||
{({ field }: FieldRenderProps<BoundSkills | undefined>) => (
|
||||
<Skills {...field} />
|
||||
)}
|
||||
</Field>
|
||||
) : null}
|
||||
<FieldArray
|
||||
name={'$$input_decorator$$.inputParameters'}
|
||||
defaultValue={[
|
||||
{ name: 'input', input: { type: ValueExpressionType.REF } },
|
||||
]}
|
||||
>
|
||||
{({
|
||||
field,
|
||||
}: FieldArrayRenderProps<{
|
||||
name: string;
|
||||
input: { type: ValueExpressionType };
|
||||
}>) => (
|
||||
<FormCard
|
||||
header={I18n.t('workflow_detail_node_parameter_input')}
|
||||
tooltip={I18n.t('workflow_detail_llm_input_tooltip')}
|
||||
>
|
||||
<div className={styles['columns-title']}>
|
||||
<ColumnsTitleWithAction
|
||||
columns={[
|
||||
{
|
||||
title: I18n.t('workflow_detail_variable_input_name'),
|
||||
style: {
|
||||
flex: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('workflow_detail_variable_input_value'),
|
||||
style: {
|
||||
flex: 3,
|
||||
},
|
||||
},
|
||||
]}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>
|
||||
{field.map((child, index) =>
|
||||
isVisionInput(child.value) ? null : (
|
||||
<div
|
||||
key={child.key}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
paddingBottom: 4,
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<Field name={`${child.name}.name`}>
|
||||
{({
|
||||
field: childNameField,
|
||||
fieldState: nameFieldState,
|
||||
}: FieldRenderProps<string>) => (
|
||||
<div
|
||||
style={{
|
||||
flex: 2,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<NodeInputName
|
||||
{...childNameField}
|
||||
input={form.getValueIn<RefExpression>(
|
||||
`${child.name}.input`,
|
||||
)}
|
||||
inputParameters={field.value || []}
|
||||
isError={!!nameFieldState?.errors?.length}
|
||||
/>
|
||||
<FormItemFeedback errors={nameFieldState?.errors} />
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name={`${child.name}.input`}>
|
||||
{({
|
||||
field: childInputField,
|
||||
fieldState: inputFieldState,
|
||||
}: FieldRenderProps<ValueExpression | undefined>) => (
|
||||
<div style={{ flex: 3, minWidth: 0 }}>
|
||||
<ValueExpressionInput
|
||||
{...childInputField}
|
||||
isError={!!inputFieldState?.errors?.length}
|
||||
/>
|
||||
<FormItemFeedback errors={inputFieldState?.errors} />
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
{readonly ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="leading-none">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
data-testid={concatTestId(child.name, 'remove')}
|
||||
icon={<IconCozMinus className="text-sm" />}
|
||||
onClick={() => {
|
||||
field.delete(index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
{isChatflow ? (
|
||||
<Field name={'$$input_decorator$$.chatHistorySetting'}>
|
||||
{({
|
||||
field: enableChatHistoryField,
|
||||
}: FieldRenderProps<{
|
||||
enableChatHistory: boolean;
|
||||
chatHistoryRound: number;
|
||||
}>) => (
|
||||
<ChatHistory
|
||||
{...enableChatHistoryField}
|
||||
style={{ paddingRight: '32px' }}
|
||||
showLine={false}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
) : null}
|
||||
{readonly ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className={styles['input-add-icon']}>
|
||||
<IconButton
|
||||
className="!block"
|
||||
color="highlight"
|
||||
size="small"
|
||||
icon={<IconCozPlus className="text-sm" />}
|
||||
onClick={() => {
|
||||
field.append({
|
||||
name: '',
|
||||
input: { type: ValueExpressionType.REF },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FormCard>
|
||||
)}
|
||||
</FieldArray>
|
||||
{!isBindDouyin ? <Vision /> : null}
|
||||
<Field
|
||||
name="$$prompt_decorator$$.systemPrompt"
|
||||
deps={['$$input_decorator$$.inputParameters']}
|
||||
defaultValue={''}
|
||||
>
|
||||
{({ field }: FieldRenderProps<string>) => (
|
||||
<>
|
||||
<SystemPrompt
|
||||
{...field}
|
||||
placeholder={I18n.t('workflow_detail_llm_sys_prompt_content')}
|
||||
fcParam={form.getValueIn('fcParam')}
|
||||
inputParameters={form.getValueIn(
|
||||
'$$input_decorator$$.inputParameters',
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
<Field
|
||||
name="$$prompt_decorator$$.prompt"
|
||||
deps={['$$input_decorator$$.inputParameters', 'model']}
|
||||
defaultValue={''}
|
||||
>
|
||||
{({ field, fieldState }: FieldRenderProps<string>) => (
|
||||
<>
|
||||
<UserPrompt field={field} fieldState={fieldState} />
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
<Field
|
||||
name={'outputs'}
|
||||
deps={['batchMode']}
|
||||
defaultValue={[{ name: 'output', type: ViewVariableType.String }]}
|
||||
>
|
||||
{({ field, fieldState }) => (
|
||||
<Outputs
|
||||
id={'llm-node-output'}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
batchMode={form.getValueIn('batchMode')}
|
||||
withDescription
|
||||
showResponseFormat
|
||||
titleTooltip={I18n.t('workflow_detail_llm_output_tooltip')}
|
||||
disabledTypes={[]}
|
||||
needErrorBody={form.getValueIn(
|
||||
'settingOnError.settingOnErrorIsOpen',
|
||||
)}
|
||||
errors={fieldState?.errors}
|
||||
sortValue={sortOutputs}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<SettingOnError outputsPath={'outputs'} batchModePath={'batchMode'} />
|
||||
</>
|
||||
</PublicScopeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const NEW_NODE_DEFAULT_VERSION = '3';
|
||||
|
||||
const userPromptFieldKey = '$$prompt_decorator$$.prompt';
|
||||
|
||||
export const LLM_FORM_META: FormMetaV2<FormData> = {
|
||||
render: props => <Render {...props} />,
|
||||
validateTrigger: ValidateTrigger.onChange,
|
||||
validate: {
|
||||
nodeMeta: nodeMetaValidate,
|
||||
outputs: llmOutputTreeMetaValidator,
|
||||
'$$input_decorator$$.inputParameters.*.name': llmInputNameValidator,
|
||||
'$$input_decorator$$.inputParameters.*.input':
|
||||
createValueExpressionInputValidate({ required: true }),
|
||||
'batch.inputLists.*.name': createNodeInputNameValidate({
|
||||
getNames: ({ formValues }) =>
|
||||
(get(formValues, 'batch.inputLists') || []).map(item => item.name),
|
||||
skipValidate: ({ formValues }) => formValues.batchMode === 'single',
|
||||
}),
|
||||
[userPromptFieldKey]: (({ value, formValues, context }) => {
|
||||
const { playgroundContext } = context;
|
||||
const modelType = get(formValues, 'model.modelType');
|
||||
const curModel = playgroundContext?.models?.find(
|
||||
model => model.model_type === modelType,
|
||||
);
|
||||
const isUserPromptRequired = curModel?.is_up_required ?? false;
|
||||
if (!isUserPromptRequired) {
|
||||
return undefined;
|
||||
}
|
||||
return value?.length
|
||||
? undefined
|
||||
: I18n.t('workflow_detail_llm_prompt_error_empty');
|
||||
}) as Validate,
|
||||
},
|
||||
effect: {
|
||||
nodeMeta: fireNodeTitleChange,
|
||||
batchMode: createProvideNodeBatchVariables('batchMode', 'batch.inputLists'),
|
||||
'batch.inputLists': createProvideNodeBatchVariables(
|
||||
'batchMode',
|
||||
'batch.inputLists',
|
||||
),
|
||||
outputs: provideNodeOutputVariablesEffect,
|
||||
model: provideReasoningContentEffect,
|
||||
},
|
||||
// eslint-disable-next-line complexity
|
||||
formatOnInit(value, context) {
|
||||
const { node, playgroundContext } = context;
|
||||
const modelsService = node.getService<WorkflowModelsService>(
|
||||
WorkflowModelsService,
|
||||
);
|
||||
const models = modelsService.getModels();
|
||||
let llmParam = get(value, 'inputs.llmParam');
|
||||
|
||||
// 初次拖入画布时:从后端返回值里,解析出来默认值。
|
||||
if (!llmParam) {
|
||||
llmParam = getDefaultLLMParams(models);
|
||||
}
|
||||
|
||||
const model: { [k: string]: unknown } = {};
|
||||
|
||||
llmParam.forEach((d: InputValueDTO) => {
|
||||
const [k, v] = reviseLLMParamPair(d);
|
||||
model[k] = v;
|
||||
});
|
||||
|
||||
const { prompt } = model;
|
||||
delete model.prompt;
|
||||
delete model.systemPrompt;
|
||||
delete model.enableChatHistory;
|
||||
|
||||
const inputParameters = get(value, 'inputs.inputParameters');
|
||||
const outputs = get(value, 'outputs');
|
||||
const isBatch = get(value, 'inputs.batch.batchEnable');
|
||||
|
||||
const initValue = {
|
||||
nodeMeta: value?.nodeMeta,
|
||||
$$input_decorator$$: {
|
||||
inputParameters: !inputParameters
|
||||
? [{ name: 'input', input: { type: ValueExpressionType.REF } }]
|
||||
: inputParameters,
|
||||
chatHistorySetting: {
|
||||
// 是否开启会话历史
|
||||
enableChatHistory:
|
||||
get(
|
||||
llmParam.find(item => item.name === 'enableChatHistory'),
|
||||
'input.value.content',
|
||||
) || false,
|
||||
|
||||
// 会话轮数,默认为 3 轮
|
||||
chatHistoryRound: Number(
|
||||
get(
|
||||
llmParam.find(item => item.name === 'chatHistoryRound'),
|
||||
'input.value.content',
|
||||
DEFAULT_CHAT_ROUND,
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
outputs: isEmpty(outputs)
|
||||
? [{ name: 'output', type: ViewVariableType.String, key: nanoid() }]
|
||||
: formatReasoningContentOnInit({
|
||||
modelsService,
|
||||
isBatch,
|
||||
outputs,
|
||||
modelType: model.modelType as number,
|
||||
}),
|
||||
|
||||
// model 会根据 llmParam 重新填充值,此时也会将之前的 chatHistoryRound 也填充上去
|
||||
// 由于在 submit 时会重新添加一个 chatHistoryRound,这里先忽略掉,避免出现问题
|
||||
model: omit(model, ['chatHistoryRound']),
|
||||
$$prompt_decorator$$: {
|
||||
prompt,
|
||||
systemPrompt: get(
|
||||
llmParam.find(item => item.name === 'systemPrompt'),
|
||||
'input.value.content',
|
||||
),
|
||||
},
|
||||
batchMode: isBatch ? 'batch' : 'single',
|
||||
batch: nodeUtils.batchToVO(get(value, 'inputs.batch'), context),
|
||||
fcParam: formatFcParamOnInit(get(value, 'inputs.fcParam')),
|
||||
};
|
||||
|
||||
// 获取后端下发 version 信息
|
||||
const schema = JSON.parse(
|
||||
playgroundContext.globalState.info?.schema_json || '{}',
|
||||
);
|
||||
const curNode = schema?.nodes?.find(_node => _node.id === node.id);
|
||||
const versionFromBackend =
|
||||
parseInt(curNode?.data?.version) >= parseInt(NEW_NODE_DEFAULT_VERSION)
|
||||
? curNode?.data?.version
|
||||
: NEW_NODE_DEFAULT_VERSION;
|
||||
// 「LLM 节点订正需求 新增节点默认为 3」
|
||||
set(initValue, 'version', versionFromBackend);
|
||||
|
||||
return initValue;
|
||||
},
|
||||
formatOnSubmit(value, context) {
|
||||
const { node, playgroundContext } = context;
|
||||
const { globalState } = playgroundContext;
|
||||
|
||||
const models = node
|
||||
.getService<WorkflowModelsService>(WorkflowModelsService)
|
||||
.getModels();
|
||||
const { model } = value;
|
||||
const modelMeta = models.find(m => m.model_type === model.modelType);
|
||||
|
||||
const llmParam = modelItemToBlockInput(model, modelMeta);
|
||||
const { batchMode } = value;
|
||||
const batchDTO = nodeUtils.batchToDTO(value.batch, context);
|
||||
|
||||
const prompt = BlockInput.createString(
|
||||
'prompt',
|
||||
value.$$prompt_decorator$$.prompt,
|
||||
);
|
||||
|
||||
const enableChatHistory = BlockInput.createBoolean(
|
||||
'enableChatHistory',
|
||||
// 工作流没有会话历史,需要设置成 false,会话流按照实际勾选的来
|
||||
globalState.isChatflow
|
||||
? Boolean(
|
||||
get(
|
||||
value,
|
||||
'$$input_decorator$$.chatHistorySetting.enableChatHistory',
|
||||
),
|
||||
)
|
||||
: false,
|
||||
);
|
||||
const chatHistoryRound = BlockInput.createInteger(
|
||||
'chatHistoryRound',
|
||||
get(value, '$$input_decorator$$.chatHistorySetting.chatHistoryRound'),
|
||||
);
|
||||
const systemPrompt = BlockInput.createString(
|
||||
'systemPrompt',
|
||||
get(value, '$$prompt_decorator$$.systemPrompt'),
|
||||
);
|
||||
llmParam.push(prompt, enableChatHistory, chatHistoryRound, systemPrompt);
|
||||
const isBatch = batchMode === 'batch';
|
||||
const formattedValue: Record<string, unknown> = {
|
||||
nodeMeta: value.nodeMeta,
|
||||
inputs: {
|
||||
inputParameters: get(value, '$$input_decorator$$.inputParameters'),
|
||||
llmParam,
|
||||
fcParam: formatFcParamOnSubmit(value.fcParam),
|
||||
batch: isBatch
|
||||
? {
|
||||
batchEnable: batchMode === 'batch',
|
||||
...batchDTO,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
outputs: formatReasoningContentOnSubmit(value.outputs, isBatch),
|
||||
/**
|
||||
* - 「LLM 节点 format优化」需求,将 outputs 内容整合到 prompt 中限制输出格式,后端需要标志位区分逻辑,版本为 2
|
||||
* - 「LLM 节点订正需求 兜底逻辑」,版本为 3
|
||||
*/
|
||||
|
||||
version: NEW_NODE_DEFAULT_VERSION,
|
||||
};
|
||||
|
||||
return formattedValue;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_NODE_META_PATH,
|
||||
type WorkflowNodeRegistry,
|
||||
} from '@coze-workflow/nodes';
|
||||
import { StandardNodeType } from '@coze-workflow/base';
|
||||
|
||||
import { type NodeTestMeta } from '@/test-run-kit';
|
||||
|
||||
import { test } from './node-test';
|
||||
import { LLM_FORM_META } from './llm-form-meta';
|
||||
|
||||
export const LLM_NODE_REGISTRY: WorkflowNodeRegistry<NodeTestMeta> = {
|
||||
type: StandardNodeType.LLM,
|
||||
meta: {
|
||||
nodeDTOType: StandardNodeType.LLM,
|
||||
style: {
|
||||
width: 360,
|
||||
},
|
||||
size: { width: 360, height: 130.7 },
|
||||
test,
|
||||
nodeMetaPath: DEFAULT_NODE_META_PATH,
|
||||
batchPath: '/batch',
|
||||
inputParametersPath: '/$$input_decorator$$/inputParameters',
|
||||
getLLMModelIdsByNodeJSON: nodeJSON =>
|
||||
nodeJSON.data.inputs.llmParam.find(p => p.name === 'modelType')?.input
|
||||
.value.content,
|
||||
helpLink: '/open/docs/guides/llm_node',
|
||||
},
|
||||
formMeta: LLM_FORM_META,
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FlowNodeFormData } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import {
|
||||
generateParametersToProperties,
|
||||
generateEnvToRelatedContextProperties,
|
||||
} from '@/test-run-kit';
|
||||
import { type NodeTestMeta } from '@/test-run-kit';
|
||||
|
||||
export const test: NodeTestMeta = {
|
||||
generateRelatedContext(node, context) {
|
||||
const { isInProject, isChatflow } = context;
|
||||
/** 不在会话流,LLM 节点无需关联环境 */
|
||||
const formData = node
|
||||
.getData(FlowNodeFormData)
|
||||
.formModel.getFormItemValueByPath('/');
|
||||
const enableChatHistory =
|
||||
formData?.$$input_decorator$$?.chatHistorySetting?.enableChatHistory;
|
||||
if (!isChatflow || !enableChatHistory) {
|
||||
return {};
|
||||
}
|
||||
return generateEnvToRelatedContextProperties({
|
||||
isNeedBot: !isInProject,
|
||||
isNeedConversation: true,
|
||||
});
|
||||
},
|
||||
generateFormBatchProperties(node) {
|
||||
const batchModePath = '/batchMode';
|
||||
const batchDataPath = '/batch';
|
||||
const { formModel } = node.getData(FlowNodeFormData);
|
||||
const batchMode = formModel.getFormItemValueByPath(batchModePath);
|
||||
if (batchMode !== 'batch') {
|
||||
return {};
|
||||
}
|
||||
const batchData = formModel.getFormItemValueByPath(batchDataPath);
|
||||
const parameters = batchData?.inputLists;
|
||||
return generateParametersToProperties(parameters, { node });
|
||||
},
|
||||
generateFormInputProperties(node) {
|
||||
const formData = node
|
||||
.getData(FlowNodeFormData)
|
||||
.formModel.getFormItemValueByPath('/');
|
||||
const parameters = formData?.$$input_decorator$$?.inputParameters;
|
||||
return generateParametersToProperties(parameters, { node });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
formatFcParamOnInit,
|
||||
formatFcParamOnSubmit,
|
||||
} from '../data-transformer';
|
||||
|
||||
describe('data-transformer', () => {
|
||||
it('formatFcParamOnInit with undefined', () => {
|
||||
expect(formatFcParamOnInit(undefined)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('formatFcParamOnInit with search_strategy', () => {
|
||||
expect(
|
||||
formatFcParamOnInit({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_strategy: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_strategy: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('formatFcParamOnInit with search_mode', () => {
|
||||
expect(
|
||||
formatFcParamOnInit({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_mode: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_strategy: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('formatFcParamOnSubmit with undefined', () => {
|
||||
expect(formatFcParamOnSubmit(undefined)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('formatFcParamOnSubmit with search_strategy', () => {
|
||||
expect(
|
||||
formatFcParamOnSubmit({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_strategy: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
knowledgeFCParam: {
|
||||
global_setting: {
|
||||
search_mode: 20,
|
||||
min_score: 0.5,
|
||||
top_k: 3,
|
||||
auto: false,
|
||||
show_source: false,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { AddIcon } from '@/nodes-v2/components/add-icon';
|
||||
|
||||
import { SkillModal, type SkillModalProps } from './skill-modal';
|
||||
|
||||
export const AddSkill: FC<
|
||||
Omit<SkillModalProps, 'visible' | 'onCancel'> & {
|
||||
disabledTooltip?: string;
|
||||
}
|
||||
> = props => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
const handleOpenModal = e => {
|
||||
e.stopPropagation();
|
||||
setModalVisible(true);
|
||||
};
|
||||
const handleCloseModal = () => setModalVisible(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<AddIcon
|
||||
disabledTooltip={props.disabledTooltip}
|
||||
onClick={handleOpenModal}
|
||||
/>
|
||||
|
||||
<SkillModal
|
||||
visible={modalVisible}
|
||||
onCancel={handleCloseModal}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { type WithCustomStyle } from '@coze-workflow/base/types';
|
||||
|
||||
import { type PluginFCSetting } from './types';
|
||||
|
||||
interface AsyncParamsFormProps {
|
||||
initValue?: PluginFCSetting['response_style'];
|
||||
onChange: (value: PluginFCSetting['response_style']) => void;
|
||||
}
|
||||
|
||||
export const AsyncParamsForm: FC<
|
||||
WithCustomStyle<AsyncParamsFormProps>
|
||||
> = props => <div>Async</div>;
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, type ReactNode } from 'react';
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { type APIParameter } from '@coze-workflow/base/api';
|
||||
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { IconCozCopy, IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tag, Toast } from '@coze-arch/coze-design';
|
||||
|
||||
import { VariableTypeTag } from '@/form-extensions/components/variable-type-tag';
|
||||
import { IconNameDescCard } from '@/form-extensions/components/icon-name-desc-card';
|
||||
|
||||
import { TooltipAction } from './tooltip-action';
|
||||
import { TypeMap } from './constants';
|
||||
|
||||
interface BoundItemCardProps {
|
||||
iconUrl?: string;
|
||||
title: string;
|
||||
pasteTitle?: string;
|
||||
params?: Array<APIParameter>;
|
||||
description?: ReactNode;
|
||||
settingRender: ReactNode;
|
||||
versionRender?: ReactNode;
|
||||
onRemove?: () => void;
|
||||
readonly?: boolean;
|
||||
hideActions?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const BoundItemCard: FC<BoundItemCardProps> = props => {
|
||||
const {
|
||||
title,
|
||||
pasteTitle,
|
||||
iconUrl,
|
||||
description,
|
||||
onRemove,
|
||||
settingRender,
|
||||
versionRender,
|
||||
params,
|
||||
readonly,
|
||||
hideActions,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
const handleCopy = () => {
|
||||
try {
|
||||
const res = copy(pasteTitle ?? title);
|
||||
if (!res) {
|
||||
throw new CustomError(ReportEventNames.copy, 'empty content');
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
id: 'plugin_copy_id',
|
||||
});
|
||||
} catch {
|
||||
Toast.warning({
|
||||
content: I18n.t('copy_failed'),
|
||||
showClose: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const actions = (
|
||||
<>
|
||||
{params ? (
|
||||
<TooltipAction
|
||||
tooltip={params.map(param => (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center mb-2 gap-1">
|
||||
{param.name}
|
||||
{TypeMap.get(param.type as number) ? (
|
||||
<VariableTypeTag size="xs">
|
||||
{TypeMap.get(param.type as number)}
|
||||
</VariableTypeTag>
|
||||
) : null}
|
||||
{param.is_required ? (
|
||||
<Tag size="mini" color="yellow">
|
||||
{I18n.t('required')}
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="font-normal coz-fg-secondary leading-4">
|
||||
{param.desc}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
icon={<IconCozInfoCircle />}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<TooltipAction
|
||||
tooltip={I18n.t('bot_edit_page_plugin_copy_tool_name_tip')}
|
||||
icon={<IconCozCopy />}
|
||||
onClick={handleCopy}
|
||||
/>
|
||||
{settingRender}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<IconNameDescCard
|
||||
icon={iconUrl}
|
||||
name={title}
|
||||
description={description as string}
|
||||
onRemove={onRemove}
|
||||
actions={hideActions ? [] : actions}
|
||||
nameSuffix={versionRender}
|
||||
readonly={readonly}
|
||||
className={className}
|
||||
descriptionTooltipPosition="bottom"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
export const defaultKnowledgeGlobalSetting = {
|
||||
auto: false,
|
||||
min_score: 0.5,
|
||||
no_recall_reply_customize_prompt: I18n.t('No_recall_006'),
|
||||
no_recall_reply_mode: 0,
|
||||
search_strategy: 0,
|
||||
show_source: false,
|
||||
show_source_mode: 0,
|
||||
top_k: 3,
|
||||
use_rerank: true,
|
||||
use_rewrite: true,
|
||||
use_nl2_sql: true,
|
||||
};
|
||||
|
||||
export const defaultResponseStyleMode = 0;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const TypeMap = new Map([
|
||||
[1, 'String'],
|
||||
[2, 'Integer'],
|
||||
[3, 'Number'],
|
||||
[4, 'Object'],
|
||||
[5, 'Array'],
|
||||
[6, 'Bool'],
|
||||
]);
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { get, isUndefined, set } from 'lodash-es';
|
||||
|
||||
import {
|
||||
type BoundKnowledgeItem,
|
||||
type BoundSkills,
|
||||
type KnowledgeGlobalSetting,
|
||||
} from './types';
|
||||
|
||||
interface KnowledgeGlobalSettingDTO extends KnowledgeGlobalSetting {
|
||||
search_mode?: number;
|
||||
}
|
||||
|
||||
interface FunctionCallParamDTO extends BoundSkills {
|
||||
knowledgeFCParam?: {
|
||||
knowledgeList?: Array<BoundKnowledgeItem>;
|
||||
global_setting?: KnowledgeGlobalSettingDTO;
|
||||
};
|
||||
}
|
||||
|
||||
type FunctionCallParamVO = BoundSkills;
|
||||
|
||||
/**
|
||||
* fc参数后端转前端
|
||||
* @param fcParamDTO
|
||||
* @returns
|
||||
*/
|
||||
export function formatFcParamOnInit(fcParamDTO?: FunctionCallParamDTO) {
|
||||
if (!fcParamDTO) {
|
||||
return fcParamDTO;
|
||||
}
|
||||
const searchMode = get(
|
||||
fcParamDTO,
|
||||
'knowledgeFCParam.global_setting.search_mode',
|
||||
);
|
||||
|
||||
if (isUndefined(searchMode)) {
|
||||
return fcParamDTO;
|
||||
}
|
||||
|
||||
delete fcParamDTO?.knowledgeFCParam?.global_setting?.search_mode;
|
||||
set(
|
||||
fcParamDTO,
|
||||
'knowledgeFCParam.global_setting.search_strategy',
|
||||
searchMode,
|
||||
);
|
||||
|
||||
return fcParamDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* fc参数前端转后端
|
||||
* @param fcParamVO
|
||||
* @returns
|
||||
*/
|
||||
export function formatFcParamOnSubmit(fcParamVO?: FunctionCallParamVO) {
|
||||
if (!fcParamVO) {
|
||||
return fcParamVO;
|
||||
}
|
||||
const searchStrategy = get(
|
||||
fcParamVO,
|
||||
'knowledgeFCParam.global_setting.search_strategy',
|
||||
);
|
||||
|
||||
if (isUndefined(searchStrategy)) {
|
||||
return fcParamVO;
|
||||
}
|
||||
|
||||
delete fcParamVO?.knowledgeFCParam?.global_setting?.search_strategy;
|
||||
set(fcParamVO, 'knowledgeFCParam.global_setting.search_mode', searchStrategy);
|
||||
|
||||
return fcParamVO;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozEmpty } from '@coze-arch/coze-design/icons';
|
||||
import { EmptyState } from '@coze-arch/coze-design';
|
||||
|
||||
export function EmptySkill() {
|
||||
return (
|
||||
<div className="flex justify-center pt-[13px] pb-[3px]">
|
||||
<EmptyState
|
||||
icon={<IconCozEmpty />}
|
||||
size="default"
|
||||
title={I18n.t('wf_chatflow_155')}
|
||||
></EmptyState>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.bound-item-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable complexity */
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag, Tooltip } from '@coze-arch/coze-design';
|
||||
import { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
|
||||
import { useGlobalState } from '@/hooks';
|
||||
import { FormCard } from '@/form-extensions/components/form-card';
|
||||
|
||||
import {
|
||||
SubWorkflowSkillVersion,
|
||||
ApiSkillVersion,
|
||||
} from '../../../components/reference-node-info';
|
||||
import { WorkflowSetting } from './workflow-setting';
|
||||
import { isSkillsEmpty, getSkillsQueryParams } from './utils';
|
||||
import { useQuerySettingDetail } from './use-query-setting-detail';
|
||||
import { useModelSkillDisabled } from './use-model-skill-disabled';
|
||||
import {
|
||||
SkillType,
|
||||
type BoundSkills,
|
||||
type BoundWorkflowItem,
|
||||
type BoundPluginItem,
|
||||
type BoundKnowledgeItem,
|
||||
type PluginFCSetting,
|
||||
type KnowledgeGlobalSetting,
|
||||
} from './types';
|
||||
import { PluginSetting } from './plugin-setting';
|
||||
import { KnowledgeSetting } from './knowledge-setting';
|
||||
import { EmptySkill } from './empty-skill';
|
||||
import { defaultKnowledgeGlobalSetting } from './constants';
|
||||
import { BoundItemCard } from './bound-item-card';
|
||||
import { AddSkill } from './add-skill';
|
||||
|
||||
interface SkillsProps {
|
||||
value?: BoundSkills;
|
||||
onChange?: (data: BoundSkills) => void;
|
||||
}
|
||||
|
||||
export const Skills: FC<SkillsProps> = props => {
|
||||
const { value = {}, onChange } = props;
|
||||
const isEmpty = isSkillsEmpty(value);
|
||||
const globalState = useGlobalState();
|
||||
const node = useCurrentEntity();
|
||||
const readonly = useReadonly();
|
||||
const modelSkillDisabled = useModelSkillDisabled();
|
||||
const disabledTooltip = modelSkillDisabled
|
||||
? I18n.t('workflow_250310_03', undefined, '该模型不支持绑定技能')
|
||||
: '';
|
||||
|
||||
const { data: skillsDetail, refetch } = useQuerySettingDetail({
|
||||
workflowId: globalState.workflowId,
|
||||
spaceId: globalState.spaceId,
|
||||
nodeId: node.id,
|
||||
...getSkillsQueryParams(value),
|
||||
});
|
||||
|
||||
const handleSkillsChange = (
|
||||
type: SkillType,
|
||||
data:
|
||||
| Array<BoundWorkflowItem>
|
||||
| Array<BoundPluginItem>
|
||||
| Array<BoundKnowledgeItem>,
|
||||
) => {
|
||||
if (type === SkillType.Plugin) {
|
||||
onChange?.({
|
||||
...value,
|
||||
pluginFCParam: {
|
||||
pluginList: data as Array<BoundPluginItem>,
|
||||
},
|
||||
});
|
||||
} else if (type === SkillType.Workflow) {
|
||||
onChange?.({
|
||||
...value,
|
||||
workflowFCParam: {
|
||||
workflowList: data as Array<BoundWorkflowItem>,
|
||||
},
|
||||
});
|
||||
} else if (type === SkillType.Knowledge) {
|
||||
onChange?.({
|
||||
...value,
|
||||
knowledgeFCParam: {
|
||||
...value.knowledgeFCParam,
|
||||
knowledgeList: data as Array<BoundKnowledgeItem>,
|
||||
global_setting:
|
||||
value.knowledgeFCParam?.global_setting ??
|
||||
defaultKnowledgeGlobalSetting,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 表单的onChange 值传递是异步,所以这里延迟下
|
||||
setTimeout(() => {
|
||||
refetch();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const handleRemovePlugin = (plugin: BoundPluginItem) => () => {
|
||||
onChange?.({
|
||||
...value,
|
||||
pluginFCParam: {
|
||||
pluginList: value?.pluginFCParam?.pluginList?.filter(
|
||||
item => item.api_id !== plugin.api_id,
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveWorkflow = (workflow: BoundWorkflowItem) => () => {
|
||||
onChange?.({
|
||||
...value,
|
||||
workflowFCParam: {
|
||||
workflowList: value?.workflowFCParam?.workflowList?.filter(
|
||||
item => item.workflow_id !== workflow.workflow_id,
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveKnowledge = (knowledge: BoundKnowledgeItem) => () => {
|
||||
onChange?.({
|
||||
...value,
|
||||
knowledgeFCParam: {
|
||||
knowledgeList: value?.knowledgeFCParam?.knowledgeList?.filter(
|
||||
item => item.id !== knowledge.id,
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handlePluginItemSettingChange =
|
||||
(plugin: BoundPluginItem) => (setting: PluginFCSetting | undefined) => {
|
||||
onChange?.({
|
||||
...value,
|
||||
pluginFCParam: {
|
||||
pluginList: value.pluginFCParam?.pluginList?.map(item => {
|
||||
if (item.api_id === plugin.api_id) {
|
||||
return {
|
||||
...item,
|
||||
fc_setting: setting,
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleWorkflowItemSettingChange =
|
||||
(workflow: BoundWorkflowItem) => (setting: PluginFCSetting | undefined) => {
|
||||
onChange?.({
|
||||
...value,
|
||||
workflowFCParam: {
|
||||
workflowList: value.workflowFCParam?.workflowList?.map(item => {
|
||||
if (item.workflow_id === workflow.workflow_id) {
|
||||
return {
|
||||
...item,
|
||||
fc_setting: setting,
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleKnowledgeGlobalSettingChange = (
|
||||
setting: KnowledgeGlobalSetting | undefined,
|
||||
) => {
|
||||
onChange?.({
|
||||
...value,
|
||||
knowledgeFCParam: {
|
||||
...value?.knowledgeFCParam,
|
||||
global_setting: setting,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FormCard
|
||||
header={I18n.t('chatflow_agent_skill_name')}
|
||||
actionButton={
|
||||
readonly ? null : (
|
||||
<AddSkill
|
||||
boundSkills={value}
|
||||
onSkillsChange={handleSkillsChange}
|
||||
disabledTooltip={disabledTooltip}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{isEmpty ? <EmptySkill /> : null}
|
||||
{value.pluginFCParam?.pluginList?.map(item => {
|
||||
const pluginDetail = skillsDetail?.plugin_detail_map?.[item.plugin_id];
|
||||
const apiDetail = skillsDetail?.plugin_api_detail_map?.[item.api_id];
|
||||
const title =
|
||||
pluginDetail && apiDetail
|
||||
? `${pluginDetail?.name} / ${apiDetail?.name}`
|
||||
: '';
|
||||
|
||||
return (
|
||||
<BoundItemCard
|
||||
title={title}
|
||||
pasteTitle={apiDetail?.name}
|
||||
description={apiDetail?.description}
|
||||
iconUrl={pluginDetail?.icon_url}
|
||||
onRemove={handleRemovePlugin(item)}
|
||||
params={apiDetail?.parameters ?? []}
|
||||
readonly={readonly}
|
||||
hideActions={modelSkillDisabled}
|
||||
versionRender={
|
||||
<div className="flex gap-1">
|
||||
<ApiSkillVersion
|
||||
versionTs={item.plugin_version}
|
||||
versionName={pluginDetail?.version_name}
|
||||
latestVersionName={pluginDetail?.latest_version_name}
|
||||
latestVersionTs={pluginDetail?.latest_version_ts}
|
||||
pluginId={item.plugin_id}
|
||||
/>
|
||||
{disabledTooltip ? (
|
||||
<Tooltip content={disabledTooltip}>
|
||||
<Tag size="mini" color="yellow">
|
||||
{I18n.t('workflow_250310_02', undefined, '已失效')}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
settingRender={
|
||||
<PluginSetting
|
||||
{...item}
|
||||
setting={item.fc_setting}
|
||||
onChange={handlePluginItemSettingChange(item)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{value.workflowFCParam?.workflowList?.map(item => {
|
||||
const detail = skillsDetail?.workflow_detail_map?.[item.workflow_id];
|
||||
return (
|
||||
<BoundItemCard
|
||||
title={detail?.name ?? ''}
|
||||
description={detail?.description}
|
||||
iconUrl={detail?.icon_url}
|
||||
onRemove={handleRemoveWorkflow(item)}
|
||||
params={detail?.api_detail?.parameters ?? []}
|
||||
versionRender={
|
||||
<SubWorkflowSkillVersion
|
||||
versionName={item.workflow_version}
|
||||
latestVersionName={detail?.latest_version_name}
|
||||
workflowId={item.workflow_id}
|
||||
/>
|
||||
}
|
||||
settingRender={
|
||||
<WorkflowSetting
|
||||
{...item}
|
||||
setting={item.fc_setting}
|
||||
onChange={handleWorkflowItemSettingChange(item)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{value.knowledgeFCParam?.knowledgeList?.map(item => {
|
||||
const detail = skillsDetail?.dataset_detail_map?.[item.id];
|
||||
return (
|
||||
<BoundItemCard
|
||||
title={detail?.name ?? ''}
|
||||
iconUrl={detail?.icon_url}
|
||||
onRemove={handleRemoveKnowledge(item)}
|
||||
settingRender={
|
||||
<KnowledgeSetting
|
||||
setting={value.knowledgeFCParam?.global_setting}
|
||||
onChange={handleKnowledgeGlobalSettingChange}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FormCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, type RefObject } from 'react';
|
||||
|
||||
import { type WithCustomStyle } from '@coze-workflow/base/types';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/bot-semi';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { Input, Switch, Table, Tag } from '@coze-arch/coze-design';
|
||||
|
||||
import { VariableTypeTag } from '@/form-extensions/components/variable-type-tag';
|
||||
|
||||
import { useTableScroll } from './use-table-scroll';
|
||||
import { type FCRequestParamsSetting, type PluginFCSetting } from './types';
|
||||
import { TypeMap } from './constants';
|
||||
|
||||
interface InputParamsFormProps {
|
||||
initValue?: PluginFCSetting['request_params'];
|
||||
onChange: (value: PluginFCSetting['request_params']) => void;
|
||||
}
|
||||
|
||||
const GAP = 55;
|
||||
|
||||
export const InputParamsForm: FC<
|
||||
WithCustomStyle<InputParamsFormProps>
|
||||
> = props => {
|
||||
const { initValue = [], onChange } = props;
|
||||
const { containerRef, scroll } = useTableScroll(GAP);
|
||||
|
||||
const handleDefaultValueChange = index => (value: string) => {
|
||||
onChange?.(
|
||||
initValue.map((item, _index) => {
|
||||
if (_index === index) {
|
||||
return {
|
||||
...item,
|
||||
local_default: value,
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const handleEnableChange = index => (checked: boolean) => {
|
||||
onChange?.(
|
||||
initValue.map((item, _index) => {
|
||||
if (_index === index) {
|
||||
return {
|
||||
...item,
|
||||
local_disable: !checked,
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef as RefObject<HTMLDivElement>} className="h-full">
|
||||
<Table
|
||||
tableProps={{
|
||||
dataSource: initValue,
|
||||
scroll,
|
||||
columns: [
|
||||
{
|
||||
title: I18n.t('Create_newtool_s3_table_name'),
|
||||
dataIndex: 'name',
|
||||
width: 300,
|
||||
render: (text, record: FCRequestParamsSetting, index) => (
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{record.name}</div>
|
||||
<VariableTypeTag>
|
||||
{TypeMap.get(record.type as number)}
|
||||
</VariableTypeTag>
|
||||
{record.is_required ? (
|
||||
<Tag color="red" size="mini">
|
||||
{I18n.t('required')}
|
||||
</Tag>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="coz-fg-dim font-normal leading-4">
|
||||
{record.desc}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t(
|
||||
'plugin_edit_tool_default_value_config_item_default_value',
|
||||
),
|
||||
dataIndex: 'defaultValue',
|
||||
width: 160,
|
||||
render: (text, record: FCRequestParamsSetting, index) => (
|
||||
<Input
|
||||
size="small"
|
||||
defaultValue={record.local_default}
|
||||
onChange={handleDefaultValueChange(index)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: () => (
|
||||
<div>
|
||||
{I18n.t('plugin_edit_tool_default_value_config_item_enable')}
|
||||
<Tooltip
|
||||
content={I18n.t(
|
||||
'plugin_bot_ide_plugin_setting_modal_item_enable_tip',
|
||||
)}
|
||||
>
|
||||
<IconInfo className="relative left-[2px] top-[2px]" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'enable',
|
||||
render: (text, record: FCRequestParamsSetting, index) => (
|
||||
<Switch
|
||||
size="mini"
|
||||
checked={record.local_disable === false ? true : false}
|
||||
onChange={handleEnableChange(index)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
.rag-mode-config-wrapper {
|
||||
> div {
|
||||
max-width: none;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
|
||||
background-color: unset;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
RagModeConfiguration,
|
||||
type IDataSetInfo,
|
||||
} from '@coze-data/knowledge-modal-base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, Modal } from '@coze-arch/coze-design';
|
||||
|
||||
import { type KnowledgeGlobalSetting } from './types';
|
||||
import { defaultKnowledgeGlobalSetting } from './constants';
|
||||
|
||||
import styles from './knowledge-setting-form-modal.module.less';
|
||||
|
||||
interface KnowledgeSettingFormModalProps {
|
||||
visible: boolean;
|
||||
setting?: KnowledgeGlobalSetting;
|
||||
onSubmit?: (setting?: KnowledgeGlobalSetting) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const KnowledgeSettingFormModal: FC<
|
||||
KnowledgeSettingFormModalProps
|
||||
> = props => {
|
||||
const { visible, onSubmit } = props;
|
||||
const [setting, updateSetting] = useState<KnowledgeGlobalSetting | undefined>(
|
||||
props.setting ?? defaultKnowledgeGlobalSetting,
|
||||
);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit?.(setting);
|
||||
};
|
||||
|
||||
const handleOnChange = (newSetting: IDataSetInfo) => {
|
||||
updateSetting({
|
||||
use_rerank: newSetting.recall_strategy?.use_rerank ?? true,
|
||||
use_rewrite: newSetting.recall_strategy?.use_rewrite ?? true,
|
||||
use_nl2_sql: newSetting.recall_strategy?.use_nl2sql ?? true,
|
||||
...omit(newSetting, ['recall_strategy']),
|
||||
});
|
||||
};
|
||||
|
||||
const getDataSetInfo = (value: KnowledgeGlobalSetting): IDataSetInfo => ({
|
||||
recall_strategy: {
|
||||
use_rerank: value.use_rerank ?? true,
|
||||
use_rewrite: value.use_rewrite ?? true,
|
||||
use_nl2sql: value.use_nl2_sql ?? true,
|
||||
},
|
||||
...omit(value, ['use_rerank', 'use_rewrite', 'use_nl2_sql']),
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="large"
|
||||
height={700}
|
||||
visible={visible}
|
||||
onCancel={props.onCancel}
|
||||
title={I18n.t('dataset_settings_title')}
|
||||
footer={
|
||||
<div>
|
||||
<Button color="hgltplus" onClick={handleSubmit}>
|
||||
{I18n.t('Save')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={styles['rag-mode-config-wrapper']}>
|
||||
<RagModeConfiguration
|
||||
dataSetInfo={getDataSetInfo(setting as KnowledgeGlobalSetting)}
|
||||
showTitle={false}
|
||||
onDataSetInfoChange={handleOnChange}
|
||||
showAuto={false}
|
||||
showSourceDisplay={false}
|
||||
showNL2SQLConfig
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { type KnowledgeGlobalSetting } from './types';
|
||||
import { TooltipAction } from './tooltip-action';
|
||||
import { KnowledgeSettingFormModal } from './knowledge-setting-form-modal';
|
||||
|
||||
interface KnowledgeSettingProps {
|
||||
setting?: KnowledgeGlobalSetting;
|
||||
onChange?: (setting?: KnowledgeGlobalSetting) => void;
|
||||
}
|
||||
|
||||
export const KnowledgeSetting: FC<KnowledgeSettingProps> = props => {
|
||||
const { setting, onChange } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleSubmit = (newSetting?: KnowledgeGlobalSetting) => {
|
||||
onChange?.(newSetting);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TooltipAction
|
||||
tooltip={I18n.t('plugin_bot_ide_plugin_setting_icon_tip')}
|
||||
icon={<IconCozSetting />}
|
||||
onClick={() => setVisible(true)}
|
||||
/>
|
||||
<KnowledgeSettingFormModal
|
||||
visible={visible}
|
||||
setting={setting}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => setVisible(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { cloneDeep, set } from 'lodash-es';
|
||||
import { type WithCustomStyle } from '@coze-workflow/base/types';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tree, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { Form, Select, Switch } from '@coze-arch/coze-design';
|
||||
|
||||
import { VariableTypeTag } from '@/form-extensions/components/variable-type-tag';
|
||||
|
||||
import { type FCResponseParamsSetting, type PluginFCSetting } from './types';
|
||||
import { TypeMap } from './constants';
|
||||
|
||||
export interface ResponseSettings {
|
||||
response_params?: PluginFCSetting['response_params'];
|
||||
response_style?: PluginFCSetting['response_style'];
|
||||
}
|
||||
|
||||
interface OutputParamsFormProps {
|
||||
initValue?: ResponseSettings;
|
||||
onChange: (value: ResponseSettings) => void;
|
||||
}
|
||||
|
||||
export const OutputParamsForm: FC<
|
||||
WithCustomStyle<OutputParamsFormProps>
|
||||
> = props => {
|
||||
const { initValue, onChange } = props;
|
||||
|
||||
const handleResponseModeChange = value => {
|
||||
onChange?.({
|
||||
...initValue,
|
||||
response_style: {
|
||||
mode: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleParamEnableChange =
|
||||
(indexPath: Array<number | string>, item: FCResponseParamsSetting) =>
|
||||
(enable: boolean) => {
|
||||
if (!initValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = cloneDeep(initValue);
|
||||
|
||||
set(newValue.response_params ?? [], indexPath.join('.'), {
|
||||
...item,
|
||||
local_disable: !enable,
|
||||
});
|
||||
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
const normalizeTreeData = (
|
||||
responseParams: PluginFCSetting['response_params'],
|
||||
indexPath: Array<number | string> = [],
|
||||
) =>
|
||||
responseParams?.map((item, index) => {
|
||||
const currentIndexPath = [...indexPath, index];
|
||||
|
||||
return {
|
||||
label: (
|
||||
<div className="flex items-center text-xs">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center">
|
||||
<div className="font-medium">{item.name}</div>
|
||||
<div>
|
||||
<VariableTypeTag>
|
||||
{TypeMap.get(item.type as number)}
|
||||
</VariableTypeTag>
|
||||
</div>
|
||||
</div>
|
||||
{item.desc ? <div className="coz-fg-dim">{item.desc}</div> : null}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: '0 0 60px',
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size="mini"
|
||||
checked={item.local_disable === true ? false : true}
|
||||
onChange={handleParamEnableChange(currentIndexPath, item)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
value: item.name,
|
||||
key: currentIndexPath.join('-'),
|
||||
children: item.sub_parameters
|
||||
? normalizeTreeData(item.sub_parameters, [
|
||||
...currentIndexPath,
|
||||
'sub_parameters',
|
||||
])
|
||||
: null,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<>
|
||||
<div>
|
||||
<Form.Label text={I18n.t('skillset_241115_01')} />
|
||||
</div>
|
||||
<Select
|
||||
className="mb-4"
|
||||
defaultValue={initValue?.response_style?.mode}
|
||||
disabled
|
||||
optionList={[
|
||||
{
|
||||
label: I18n.t('skillset_241115_02'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: I18n.t('skillset_241115_03'),
|
||||
value: 0,
|
||||
},
|
||||
]}
|
||||
onChange={handleResponseModeChange}
|
||||
/>
|
||||
</>
|
||||
<>
|
||||
<div>
|
||||
<Form.Label text={I18n.t('workflow_detail_end_output')} />
|
||||
</div>
|
||||
<div className="text-xs text-left coz-fg-secondary coz-mg-hglt py-2 px-3 rounded-lg">
|
||||
{I18n.t('plugin_bot_ide_plugin_setting_modal_item_enable_tip')}
|
||||
</div>
|
||||
<div className="flex text-xs coz-fg-dim font-medium mt-3">
|
||||
<div className="flex-1 pl-[26px]">
|
||||
{I18n.t('Create_newtool_s3_table_name')}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: '0 0 60px',
|
||||
}}
|
||||
>
|
||||
{I18n.t('plugin_edit_tool_default_value_config_item_enable')}
|
||||
<Tooltip content={I18n.t('plugin_bot_ide_output_param_enable_tip')}>
|
||||
<IconInfo className="relative left-[2px] top-[3px]" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<Tree
|
||||
treeData={normalizeTreeData(initValue?.response_params)}
|
||||
defaultExpandAll
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.plugin-setting-form-modal {
|
||||
:global(.semi-modal-body-wrapper) {
|
||||
height: calc(100% - 48px);
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
:global(.semi-modal-body) {
|
||||
height: 100%;
|
||||
|
||||
> :global(div) {
|
||||
max-height: none !important;
|
||||
|
||||
>:global(div) {
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.semi-modal-content) {
|
||||
gap: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:global(.semi-navigation-list-wrapper) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
:global(.semi-navigation-item-selected) {
|
||||
background-color: var(--coz-mg-primary) !important;
|
||||
}
|
||||
|
||||
:global(.semi-navigation-item-normal:hover) {
|
||||
background-color: var(--coz-mg-primary) !important;
|
||||
}
|
||||
|
||||
:global(.semi-modal-close) {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-setting-nav {
|
||||
width: 188px !important;
|
||||
padding: 0;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Nav,
|
||||
NavItem,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { type PluginFCSetting } from './types';
|
||||
import { OutputParamsForm, type ResponseSettings } from './output-params-form';
|
||||
import { InputParamsForm } from './input-params-form';
|
||||
|
||||
import styles from './plugin-setting-form-modal.module.less';
|
||||
|
||||
interface PluginSettingFormModalProps {
|
||||
visible: boolean;
|
||||
setting?: PluginFCSetting;
|
||||
onSubmit?: (setting?: PluginFCSetting) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
enum SettingNavKey {
|
||||
Input = 'input',
|
||||
Output = 'output',
|
||||
}
|
||||
|
||||
export const PluginSettingFormModal: FC<
|
||||
PluginSettingFormModalProps
|
||||
> = props => {
|
||||
const { visible, setting: originSetting, onSubmit, onCancel } = props;
|
||||
const [activeKey, setActiveKey] = useState(SettingNavKey.Input);
|
||||
const [setting, updateSetting] = useState<PluginFCSetting | undefined>(
|
||||
originSetting,
|
||||
);
|
||||
|
||||
const handleParamsChange =
|
||||
(key: keyof PluginFCSetting) =>
|
||||
(settingParams: PluginFCSetting[keyof PluginFCSetting]) => {
|
||||
updateSetting({
|
||||
...setting,
|
||||
[key]: settingParams,
|
||||
});
|
||||
};
|
||||
|
||||
const handleResponseParamsChange = (responseSettings: ResponseSettings) => {
|
||||
updateSetting({
|
||||
...setting,
|
||||
...responseSettings,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit?.(setting);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={styles['plugin-setting-form-modal']}
|
||||
size="xl"
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
bodyStyle={{
|
||||
padding: 0,
|
||||
}}
|
||||
height={700}
|
||||
footer={
|
||||
<div className="pt-0 flex">
|
||||
<div
|
||||
className="coz-bg-primary p-4"
|
||||
style={{
|
||||
flex: '0 0 220px',
|
||||
}}
|
||||
></div>
|
||||
<div className="flex-1 pb-4 pr-4">
|
||||
<Button color="hgltplus" onClick={handleSubmit}>
|
||||
{I18n.t('Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex h-full">
|
||||
<div
|
||||
className="coz-bg-primary p-4"
|
||||
style={{
|
||||
flex: '0 0 220px',
|
||||
}}
|
||||
>
|
||||
<Typography.Title heading={4} className="!mb-4 px-2">
|
||||
{I18n.t('basic_setting')}
|
||||
</Typography.Title>
|
||||
<Nav
|
||||
className={styles['plugin-setting-nav']}
|
||||
selectedKeys={[activeKey]}
|
||||
onSelect={({ itemKey }) => setActiveKey(itemKey as SettingNavKey)}
|
||||
>
|
||||
<NavItem
|
||||
itemKey={SettingNavKey.Input}
|
||||
text={I18n.t('Create_newtool_s2_title')}
|
||||
></NavItem>
|
||||
<NavItem
|
||||
itemKey={SettingNavKey.Output}
|
||||
text={I18n.t('Create_newtool_s3_Outputparameters')}
|
||||
></NavItem>
|
||||
</Nav>
|
||||
</div>
|
||||
<div className="flex-1 px-4 pt-[50px] relative h-full overflow-y-hidden">
|
||||
<div
|
||||
className={classnames(
|
||||
{
|
||||
hidden: activeKey !== SettingNavKey.Input,
|
||||
block: activeKey === SettingNavKey.Input,
|
||||
},
|
||||
'h-full',
|
||||
)}
|
||||
>
|
||||
<InputParamsForm
|
||||
initValue={setting?.request_params}
|
||||
onChange={handleParamsChange('request_params')}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
{
|
||||
hidden: activeKey !== SettingNavKey.Output,
|
||||
block: activeKey === SettingNavKey.Output,
|
||||
},
|
||||
'h-full',
|
||||
)}
|
||||
>
|
||||
<OutputParamsForm
|
||||
initValue={{
|
||||
response_params: setting?.response_params,
|
||||
response_style: setting?.response_style,
|
||||
}}
|
||||
onChange={handleResponseParamsChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
import { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useQueryLatestFCSettings } from './use-query-latest-fc-settings';
|
||||
import { type BoundPluginItem, type PluginFCSetting } from './types';
|
||||
import { TooltipAction } from './tooltip-action';
|
||||
import { PluginSettingFormModal } from './plugin-setting-form-modal';
|
||||
import { defaultResponseStyleMode } from './constants';
|
||||
|
||||
interface PluginSettingProps extends BoundPluginItem {
|
||||
setting?: PluginFCSetting;
|
||||
onChange?: (setting?: PluginFCSetting) => void;
|
||||
}
|
||||
|
||||
export const PluginSetting: FC<PluginSettingProps> = props => {
|
||||
const { setting, onChange } = props;
|
||||
const node = useCurrentEntity();
|
||||
const mutation = useQueryLatestFCSettings({
|
||||
nodeId: node.id,
|
||||
});
|
||||
const [latestSetting, setLatestSetting] = useState<
|
||||
PluginFCSetting | undefined
|
||||
>(undefined);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleEdit = () => {
|
||||
mutation.mutate(
|
||||
{
|
||||
pluginFCSetting: setting
|
||||
? {
|
||||
plugin_id: props.plugin_id,
|
||||
api_id: props.api_id,
|
||||
api_name: props.api_name,
|
||||
is_draft: props.is_draft,
|
||||
plugin_version: props.plugin_version,
|
||||
...setting,
|
||||
}
|
||||
: {
|
||||
plugin_id: props.plugin_id,
|
||||
api_id: props.api_id,
|
||||
api_name: props.api_name,
|
||||
request_params: [],
|
||||
response_params: [],
|
||||
response_style: {
|
||||
mode: defaultResponseStyleMode,
|
||||
},
|
||||
is_draft: props.is_draft,
|
||||
plugin_version: props.plugin_version,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: res => {
|
||||
setLatestSetting(res?.plugin_fc_setting);
|
||||
setVisible(true);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = (newSetting?: PluginFCSetting) => {
|
||||
onChange?.(newSetting);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TooltipAction
|
||||
tooltip={I18n.t('plugin_bot_ide_plugin_setting_icon_tip')}
|
||||
icon={<IconCozSetting />}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
{visible ? (
|
||||
<PluginSettingFormModal
|
||||
visible={visible}
|
||||
setting={latestSetting}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => setVisible(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozKnowledgeFill } from '@coze-arch/coze-design/icons';
|
||||
|
||||
export enum SkillKnowledgeSiderCategory {
|
||||
Library = 'library',
|
||||
Project = 'project',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
projectId?: string;
|
||||
category?: string;
|
||||
setCategory?: (category: SkillKnowledgeSiderCategory) => void;
|
||||
}
|
||||
|
||||
interface SiderCategoryProps {
|
||||
label: string;
|
||||
selected: boolean;
|
||||
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const SiderCategory = ({ label, onClick, selected }: SiderCategoryProps) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={classNames([
|
||||
'flex items-center gap-[8px] px-[12px]',
|
||||
'px-[12px] py-[6px] rounded-[8px]',
|
||||
'cursor-pointer',
|
||||
'hover:text-[var(--light-usage-text-color-text-0,#1c1f23)]',
|
||||
'hover:bg-[var(--light-usage-fill-color-fill-0,rgba(46,50,56,5%))]',
|
||||
selected &&
|
||||
'text-[var(--light-usage-text-color-text-0,#1c1d23)] bg-[var(--light-usage-fill-color-fill-0,rgba(46,47,56,5%))]',
|
||||
])}
|
||||
>
|
||||
<IconCozKnowledgeFill />
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const SkillKnowledgeSider: React.FC<Props> = ({
|
||||
projectId,
|
||||
category = SkillKnowledgeSiderCategory.Library,
|
||||
setCategory,
|
||||
}) => (
|
||||
<>
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_library_resources', {
|
||||
resource: I18n.t('resource_type_knowledge'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory?.(SkillKnowledgeSiderCategory.Library);
|
||||
}}
|
||||
selected={category === SkillKnowledgeSiderCategory.Library}
|
||||
/>
|
||||
{projectId ? (
|
||||
<SiderCategory
|
||||
label={I18n.t('project_resource_modal_project_resources', {
|
||||
resource: I18n.t('resource_type_knowledge'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setCategory?.(SkillKnowledgeSiderCategory.Project);
|
||||
}}
|
||||
selected={category === SkillKnowledgeSiderCategory.Project}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.sider {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 218px;
|
||||
padding: 12px;
|
||||
|
||||
background-color: #ebedf0;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
height: 100%;
|
||||
|
||||
background: #f7f7fa;
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.tool-tag-list) {
|
||||
overflow: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.data-sets-content {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { ModalI18nKey } from '@coze-workflow/components/workflow-modal';
|
||||
import {
|
||||
WorkflowModalFrom,
|
||||
useWorkflowModalParts,
|
||||
} from '@coze-workflow/components';
|
||||
import { KnowledgeListModalContent } from '@coze-data/knowledge-modal-adapter';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UITabsModal } from '@coze-arch/bot-semi';
|
||||
import { type Dataset } from '@coze-arch/bot-api/knowledge';
|
||||
import { From } from '@coze-agent-ide/plugin-shared';
|
||||
import { usePluginModalParts } from '@coze-agent-ide/bot-plugin-export/agentSkillPluginModal/hooks';
|
||||
|
||||
import { useGlobalState, useSpaceId, useNodeVersionService } from '@/hooks';
|
||||
|
||||
import { isDraftByProjectId } from './utils';
|
||||
import {
|
||||
SkillType,
|
||||
type BoundSkills,
|
||||
type BoundWorkflowItem,
|
||||
type BoundPluginItem,
|
||||
type BoundKnowledgeItem,
|
||||
} from './types';
|
||||
import {
|
||||
SkillKnowledgeSider,
|
||||
SkillKnowledgeSiderCategory,
|
||||
} from './skill-knowledge-sider';
|
||||
|
||||
import s from './skill-modal.module.less';
|
||||
|
||||
export interface SkillModalProps {
|
||||
visible: boolean;
|
||||
onSkillsChange: (
|
||||
type: SkillType,
|
||||
data:
|
||||
| Array<BoundWorkflowItem>
|
||||
| Array<BoundPluginItem>
|
||||
| Array<BoundKnowledgeItem>,
|
||||
) => void;
|
||||
boundSkills?: BoundSkills;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const SkillModal: FC<SkillModalProps> = props => {
|
||||
const { visible, onSkillsChange, boundSkills, onCancel } = props;
|
||||
|
||||
const { projectId, getProjectApi, playgroundProps } = useGlobalState();
|
||||
const windowRef = useRef<WindowProxy | null>();
|
||||
const spaceID = useSpaceId();
|
||||
|
||||
const nodeVersionService = useNodeVersionService();
|
||||
const [category, setCategory] = useState<SkillKnowledgeSiderCategory>(
|
||||
SkillKnowledgeSiderCategory.Library,
|
||||
);
|
||||
|
||||
// plugin 添加弹窗
|
||||
const pluginModalFrom = projectId
|
||||
? From.ProjectWorkflow
|
||||
: From.WorkflowAddNode;
|
||||
|
||||
const getOnSkillsChange = (type: SkillType) => data =>
|
||||
onSkillsChange(type, data);
|
||||
|
||||
const [activeKey, setActiveKey] = useState<SkillType>(SkillType.Plugin);
|
||||
const pluginModalParts = usePluginModalParts({
|
||||
pluginApiList: boundSkills?.pluginFCParam?.pluginList ?? [],
|
||||
onPluginApiListChange: getOnSkillsChange(SkillType.Plugin),
|
||||
from: pluginModalFrom,
|
||||
projectId,
|
||||
openModeCallback: async val => {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await nodeVersionService.addApiCheck(val.plugin_id, val.version_ts))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
onSkillsChange(SkillType.Plugin, [
|
||||
...(boundSkills?.pluginFCParam?.pluginList ?? []),
|
||||
{
|
||||
plugin_id: val.plugin_id as string,
|
||||
api_id: val.api_id as string,
|
||||
api_name: val.name as string,
|
||||
plugin_version: val.version_ts || '',
|
||||
is_draft: isDraftByProjectId(val.project_id),
|
||||
},
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
const sourceTitle = I18n.t('workflow_241119_01');
|
||||
const workflowModalParts = useWorkflowModalParts({
|
||||
from: projectId
|
||||
? WorkflowModalFrom.ProjectWorkflowAddNode
|
||||
: WorkflowModalFrom.WorkflowAddNode,
|
||||
projectId,
|
||||
workFlowList: (boundSkills?.workflowFCParam?.workflowList ?? []).map(
|
||||
item => ({
|
||||
workflow_id: item.workflow_id,
|
||||
plugin_id: item.plugin_id,
|
||||
name: '',
|
||||
desc: '',
|
||||
parameters: [],
|
||||
plugin_icon: '',
|
||||
}),
|
||||
),
|
||||
onWorkFlowListChange: () => null,
|
||||
onAdd: async (val, config) => {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await nodeVersionService.addSubWorkflowCheck(
|
||||
val.workflow_id,
|
||||
val.version_name,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
onSkillsChange(SkillType.Workflow, [
|
||||
...(boundSkills?.workflowFCParam?.workflowList ?? []),
|
||||
{
|
||||
plugin_id: val.plugin_id,
|
||||
workflow_id: val.workflow_id,
|
||||
plugin_version: '',
|
||||
workflow_version: val.version_name || '',
|
||||
is_draft: isDraftByProjectId(val.project_id),
|
||||
},
|
||||
]);
|
||||
},
|
||||
onRemove: val => {
|
||||
onSkillsChange(
|
||||
SkillType.Workflow,
|
||||
(boundSkills?.workflowFCParam?.workflowList ?? []).filter(
|
||||
item => item.workflow_id !== val.workflow_id,
|
||||
),
|
||||
);
|
||||
},
|
||||
i18nMap: {
|
||||
[ModalI18nKey.ListItemRemove]: {
|
||||
key: 'scene_workflow_delete_workflow_button',
|
||||
options: { source: sourceTitle },
|
||||
},
|
||||
[ModalI18nKey.ListItemRemoveConfirmTitle]: {
|
||||
key: 'scene_workflow_delete_workflow_popup_title',
|
||||
options: { source: sourceTitle },
|
||||
},
|
||||
[ModalI18nKey.ListItemRemoveConfirmDescription]: {
|
||||
key: 'scene_workflow_delete_workflow_popup_subtitle',
|
||||
options: { source: sourceTitle },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const handleKnowledgeListChange = (data: Dataset[]) => {
|
||||
onSkillsChange(
|
||||
SkillType.Knowledge,
|
||||
data?.map(item => ({
|
||||
id: item.dataset_id as string,
|
||||
name: item.name as string,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<UITabsModal
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
tabs={{
|
||||
tabsProps: {
|
||||
lazyRender: true,
|
||||
activeKey,
|
||||
onChange: (key: string) => setActiveKey(key as SkillType),
|
||||
},
|
||||
tabPanes: [
|
||||
{
|
||||
tabPaneProps: {
|
||||
tab: I18n.t('Tools'),
|
||||
itemKey: SkillType.Plugin,
|
||||
},
|
||||
content: (
|
||||
<div className={s.main}>
|
||||
<div className={s.sider}>{pluginModalParts.sider}</div>
|
||||
<div className={s.content}>
|
||||
<div className={s.filter}>{pluginModalParts.filter}</div>
|
||||
<div className={s['content-inner']}>
|
||||
{pluginModalParts.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
tabPaneProps: {
|
||||
tab: I18n.t('Workflow'),
|
||||
itemKey: SkillType.Workflow,
|
||||
},
|
||||
content: (
|
||||
<div className={s.main}>
|
||||
<div className={s.sider}>{workflowModalParts.sider}</div>
|
||||
<div className={s.content}>
|
||||
<div className={s.filter}>{workflowModalParts.filter}</div>
|
||||
<div className={s['content-inner']}>
|
||||
{workflowModalParts.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
tabPaneProps: {
|
||||
tab: I18n.t('Datasets'),
|
||||
itemKey: SkillType.Knowledge,
|
||||
},
|
||||
content: (
|
||||
<div className={s.main}>
|
||||
{projectId ? (
|
||||
<div className={s.sider}>
|
||||
<SkillKnowledgeSider
|
||||
projectId={projectId}
|
||||
category={category}
|
||||
setCategory={setCategory}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={classNames(s.content, s['data-sets-content'])}>
|
||||
<div className={s['content-inner']}>
|
||||
<KnowledgeListModalContent
|
||||
projectID={
|
||||
category === SkillKnowledgeSiderCategory.Project
|
||||
? projectId
|
||||
: undefined
|
||||
}
|
||||
datasetList={(
|
||||
boundSkills?.knowledgeFCParam?.knowledgeList ?? []
|
||||
).map(item => ({
|
||||
dataset_id: item.id,
|
||||
name: item.name,
|
||||
}))}
|
||||
onDatasetListChange={handleKnowledgeListChange}
|
||||
beforeCreate={shouldUpload => {
|
||||
if (shouldUpload && !projectId) {
|
||||
windowRef.current = window.open();
|
||||
}
|
||||
}}
|
||||
onClickAddKnowledge={(id, unitType, shouldUpload) => {
|
||||
if (shouldUpload) {
|
||||
if (projectId) {
|
||||
const IDENav = getProjectApi()?.navigate;
|
||||
IDENav?.(
|
||||
`/knowledge/${id}?module=upload&type=${unitType}`,
|
||||
);
|
||||
} else if (windowRef.current) {
|
||||
if (id) {
|
||||
windowRef.current.location = `/space/${spaceID}/knowledge/${id}/upload?type=${unitType}`;
|
||||
} else {
|
||||
windowRef.current.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (projectId) {
|
||||
playgroundProps.refetchProjectResourceList?.();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { TooltipAction } from '../../../form-extensions/components/icon-name-desc-card/';
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IDataSetInfo } from '@coze-data/knowledge-modal-base';
|
||||
import {
|
||||
type FCPluginSetting,
|
||||
type APIParameter,
|
||||
type FCWorkflowSetting,
|
||||
} from '@coze-arch/idl/workflow_api';
|
||||
|
||||
export enum SkillType {
|
||||
Plugin = 'plugin',
|
||||
Workflow = 'workflow',
|
||||
Knowledge = 'knowledge',
|
||||
}
|
||||
|
||||
export type PluginFCParamsSetting = APIParameter;
|
||||
|
||||
export type FCRequestParamsSetting = PluginFCParamsSetting;
|
||||
export type FCResponseParamsSetting = PluginFCParamsSetting;
|
||||
export interface FCResponseStyleSetting {
|
||||
mode: number;
|
||||
}
|
||||
|
||||
export type PluginFCSetting = FCPluginSetting;
|
||||
export type WorkflowFCSetting = FCWorkflowSetting;
|
||||
|
||||
export interface BoundWorkflowItem {
|
||||
plugin_id: string;
|
||||
workflow_id: string;
|
||||
// 如果是project 填project version,资源库填plugin version
|
||||
plugin_version: string;
|
||||
workflow_version: string;
|
||||
// 如果是project 就填true,资源库 false
|
||||
is_draft: boolean;
|
||||
fc_setting?: WorkflowFCSetting;
|
||||
}
|
||||
|
||||
export interface BoundPluginItem {
|
||||
plugin_id: string;
|
||||
api_id: string;
|
||||
api_name: string;
|
||||
// 如果是project 填project version,资源库填plugin version
|
||||
plugin_version: string;
|
||||
// 如果是project 就填true,资源库 false
|
||||
is_draft: boolean;
|
||||
fc_setting?: PluginFCSetting;
|
||||
}
|
||||
|
||||
export interface BoundKnowledgeItem {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type KnowledgeGlobalSetting = Omit<IDataSetInfo, 'recall_strategy'> & {
|
||||
use_rerank: boolean;
|
||||
use_rewrite: boolean;
|
||||
use_nl2_sql: boolean;
|
||||
};
|
||||
|
||||
export interface BoundSkills {
|
||||
workflowFCParam?: {
|
||||
workflowList?: Array<BoundWorkflowItem>;
|
||||
};
|
||||
pluginFCParam?: {
|
||||
pluginList?: Array<BoundPluginItem>;
|
||||
};
|
||||
knowledgeFCParam?: {
|
||||
knowledgeList?: Array<BoundKnowledgeItem>;
|
||||
global_setting?: KnowledgeGlobalSetting;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { WorkflowModelsService } from '@/services';
|
||||
|
||||
import { useModelType } from '../hooks/use-model-type';
|
||||
|
||||
/**
|
||||
* 判断模型是不是支持技能
|
||||
*/
|
||||
export function useModelSkillDisabled() {
|
||||
const modelType = useModelType();
|
||||
|
||||
const modelsService = useService(WorkflowModelsService);
|
||||
return !(modelType && modelsService.isFunctionCallModel(modelType));
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { workflowApi } from '@coze-workflow/base/api';
|
||||
|
||||
import { useGlobalState } from '@/hooks';
|
||||
|
||||
import {
|
||||
type WorkflowFCSetting,
|
||||
type KnowledgeGlobalSetting,
|
||||
type PluginFCSetting,
|
||||
} from './types';
|
||||
|
||||
// const mockSettings = {
|
||||
// plugin_fc_setting: {
|
||||
// request_params: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// sub_parameters: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// response_params: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// sub_parameters: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// response_style: {
|
||||
// mode: 1, // Raw = 0, // 原始输出 Card = 1, // 渲染成卡片 Template = 2, // 包含变量的模板内容,用jinja2渲染 TODO
|
||||
// },
|
||||
// },
|
||||
// workflow_fc_setting: {
|
||||
// request_params: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// sub_parameters: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// response_params: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// sub_parameters: [
|
||||
// {
|
||||
// name: 'fieldName',
|
||||
// type: 1, // String = 1, Integer = 2, Number = 3, Object = 4, Array = 5, Bool = 6,
|
||||
// sub_type: 1,
|
||||
// location: 1, // Path = 1, Query = 2, Body = 3, Header = 4,
|
||||
// is_required: false,
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// local_default: '', // 默认值
|
||||
// local_disable: false, // 是否启用
|
||||
// assist_type: 1, //DEFAULT = 1, IMAGE = 2, DOC = 3,CODE = 4,PPT = 5, TXT = 6, EXCEL = 7, AUDIO = 8, ZIP = 9,VIDEO = 10,
|
||||
// },
|
||||
// ],
|
||||
// response_style: {
|
||||
// mode: 1, // Raw = 0, // 原始输出 Card = 1, // 渲染成卡片 Template = 2, // 包含变量的模板内容,用jinja2渲染 TODO
|
||||
// },
|
||||
// },
|
||||
// dataset_fc_setting: {
|
||||
// top_k: 5, // 召回数量
|
||||
// min_score: 0.46, // 召回的最小相似度阈值
|
||||
// auto: true, // 是否自动召回
|
||||
// search_mode: 1, // 搜索策略
|
||||
// no_recall_reply_mode: 1, // 无召回回复mode,默认0
|
||||
// no_recall_reply_customize_prompt:
|
||||
// '抱歉,您的问题超出了我的知识范围,并且无法在当前阶段回答', // 无召回回复时自定义prompt,当NoRecallReplyMode=1时生效
|
||||
// show_source: true, // 是否展示来源
|
||||
// show_source_mode: 1, // 来源展示方式 默认值0 卡片列表方式
|
||||
// },
|
||||
// }
|
||||
|
||||
export const useQueryLatestFCSettings = (params: { nodeId: string }) => {
|
||||
const { workflowId, spaceId } = useGlobalState();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (options: {
|
||||
pluginFCSetting?: PluginFCSetting;
|
||||
workflowFCSetting?: WorkflowFCSetting;
|
||||
datasetFCSetting?: KnowledgeGlobalSetting;
|
||||
}) =>
|
||||
workflowApi.GetLLMNodeFCSettingsMerged({
|
||||
workflow_id: workflowId,
|
||||
space_id: spaceId,
|
||||
plugin_fc_setting: options.pluginFCSetting,
|
||||
workflow_fc_setting: options.workflowFCSetting,
|
||||
}),
|
||||
// return Promise.resolve({
|
||||
// data: mockSettings,
|
||||
// });
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useQuery, type UseQueryResult } from '@tanstack/react-query';
|
||||
import {
|
||||
type DatasetFCItem,
|
||||
type GetLLMNodeFCSettingDetailResponse,
|
||||
type PluginFCItem,
|
||||
workflowApi,
|
||||
type WorkflowFCItem,
|
||||
} from '@coze-workflow/base/api';
|
||||
|
||||
import { PromiseLimiter } from '@/utils/promise-limiter';
|
||||
|
||||
// 限制并发,因为同一个流程上可能会有很多LLM节点,同时请求
|
||||
const CONCURRENCY = 3;
|
||||
|
||||
const limiter = new PromiseLimiter(CONCURRENCY, true);
|
||||
|
||||
export const useQuerySettingDetail = (params: {
|
||||
workflowId: string;
|
||||
spaceId: string;
|
||||
nodeId: string;
|
||||
plugin_list?: Array<PluginFCItem>;
|
||||
workflow_list?: Array<WorkflowFCItem>;
|
||||
dataset_list?: Array<DatasetFCItem>;
|
||||
enabled?: boolean;
|
||||
}): UseQueryResult<GetLLMNodeFCSettingDetailResponse> => {
|
||||
const { nodeId, enabled = true } = params;
|
||||
return useQuery({
|
||||
queryKey: [nodeId, 'settingDetail'],
|
||||
queryFn: () =>
|
||||
limiter.run(() =>
|
||||
workflowApi.GetLLMNodeFCSettingDetail({
|
||||
workflow_id: params.workflowId,
|
||||
space_id: params.spaceId,
|
||||
plugin_list: params.plugin_list,
|
||||
workflow_list: params.workflow_list,
|
||||
dataset_list: params.dataset_list,
|
||||
}),
|
||||
),
|
||||
enabled,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMemo, useRef } from 'react';
|
||||
|
||||
import { useSize } from 'ahooks';
|
||||
|
||||
export function useTableScroll(gap: number) {
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
|
||||
const size = useSize(containerRef);
|
||||
const scroll = useMemo(
|
||||
() => ({ y: size?.height ? size.height - gap : 0 }),
|
||||
[size, gap],
|
||||
);
|
||||
|
||||
return {
|
||||
containerRef,
|
||||
scroll,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type BoundSkills } from './types';
|
||||
|
||||
/**
|
||||
* 根据projectId判断是否是草稿
|
||||
* 资源库里面的插件 project_id = '0'
|
||||
*/
|
||||
export function isDraftByProjectId(projectId?: string) {
|
||||
return projectId && projectId !== '0' ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 技能是否为空
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
export function isSkillsEmpty(value: BoundSkills) {
|
||||
return (
|
||||
!value.pluginFCParam?.pluginList?.length &&
|
||||
!value.workflowFCParam?.workflowList?.length &&
|
||||
!value.knowledgeFCParam?.knowledgeList?.length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取技能查询参数
|
||||
* @param fcParam
|
||||
* @returns
|
||||
*/
|
||||
export function getSkillsQueryParams(boundSkills?: BoundSkills) {
|
||||
return {
|
||||
plugin_list: boundSkills?.pluginFCParam?.pluginList?.map(item => ({
|
||||
plugin_id: item.plugin_id,
|
||||
api_id: item.api_id,
|
||||
api_name: item.api_name,
|
||||
is_draft: item.is_draft,
|
||||
plugin_version: item.plugin_version,
|
||||
})),
|
||||
workflow_list: boundSkills?.workflowFCParam?.workflowList?.map(item => ({
|
||||
workflow_id: item.workflow_id,
|
||||
plugin_id: item.plugin_id,
|
||||
is_draft: item.is_draft,
|
||||
workflow_version: item.workflow_version,
|
||||
})),
|
||||
dataset_list: boundSkills?.knowledgeFCParam?.knowledgeList?.map(item => ({
|
||||
dataset_id: item.id,
|
||||
is_draft: false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
import { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useQueryLatestFCSettings } from './use-query-latest-fc-settings';
|
||||
import { type BoundWorkflowItem, type PluginFCSetting } from './types';
|
||||
import { TooltipAction } from './tooltip-action';
|
||||
import { PluginSettingFormModal } from './plugin-setting-form-modal';
|
||||
import { defaultResponseStyleMode } from './constants';
|
||||
|
||||
interface WorkflowSettingProps extends BoundWorkflowItem {
|
||||
setting?: PluginFCSetting;
|
||||
onChange?: (setting?: PluginFCSetting) => void;
|
||||
}
|
||||
|
||||
export const WorkflowSetting: FC<WorkflowSettingProps> = props => {
|
||||
const { setting, onChange } = props;
|
||||
const node = useCurrentEntity();
|
||||
const mutation = useQueryLatestFCSettings({
|
||||
nodeId: node.id,
|
||||
});
|
||||
const [latestSetting, setLatestSetting] = useState<
|
||||
PluginFCSetting | undefined
|
||||
>(undefined);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleEdit = () => {
|
||||
mutation.mutate(
|
||||
{
|
||||
workflowFCSetting: setting
|
||||
? {
|
||||
workflow_id: props.workflow_id,
|
||||
plugin_id: props.plugin_id,
|
||||
is_draft: props.is_draft,
|
||||
workflow_version: props.workflow_version,
|
||||
...setting,
|
||||
}
|
||||
: {
|
||||
workflow_id: props.workflow_id,
|
||||
plugin_id: props.plugin_id,
|
||||
request_params: [],
|
||||
response_params: [],
|
||||
workflow_version: props.workflow_version,
|
||||
response_style: {
|
||||
mode: defaultResponseStyleMode,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: res => {
|
||||
setLatestSetting(res?.worflow_fc_setting);
|
||||
setVisible(true);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = (newSetting?: PluginFCSetting) => {
|
||||
onChange?.(newSetting);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TooltipAction
|
||||
tooltip={I18n.t('plugin_bot_ide_plugin_setting_icon_tip')}
|
||||
icon={<IconCozSetting />}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
{visible ? (
|
||||
<PluginSettingFormModal
|
||||
visible={visible}
|
||||
setting={latestSetting}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => setVisible(false)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { useForm, useRefresh } from '@flowgram-adapter/free-layout-editor';
|
||||
import type { ILibraryItem } from '@coze-common/editor-plugins/library-insert';
|
||||
import type { InputValueVO } from '@coze-workflow/base';
|
||||
|
||||
import { addSKillFromLibrary } from '@/nodes-v2/llm/utils';
|
||||
|
||||
import type { BoundSkills } from '../skills/types';
|
||||
import {
|
||||
SystemPrompt as DefaultSystemPrompt,
|
||||
type SystemPromptProps,
|
||||
} from '../../components/system-prompt';
|
||||
import useSkillLibraries from './use-skill-libraries';
|
||||
|
||||
interface Props extends Omit<SystemPromptProps, 'libraries'> {
|
||||
inputParameters?: InputValueVO[];
|
||||
fcParam?: BoundSkills;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const SystemPrompt = (props: Props) => {
|
||||
const { placeholder, inputParameters, fcParam, onAddLibrary, ...rest } =
|
||||
props;
|
||||
|
||||
const form = useForm();
|
||||
const refresh = useRefresh();
|
||||
|
||||
const { libraries, refetch } = useSkillLibraries({ fcParam });
|
||||
|
||||
const handleAddLibrary = useCallback(
|
||||
(library: ILibraryItem) => {
|
||||
form.setValueIn(
|
||||
'fcParam',
|
||||
addSKillFromLibrary(library, form.getValueIn('fcParam')),
|
||||
);
|
||||
|
||||
refresh();
|
||||
|
||||
setTimeout(() => {
|
||||
refetch();
|
||||
}, 10);
|
||||
},
|
||||
[onAddLibrary],
|
||||
);
|
||||
|
||||
return (
|
||||
<DefaultSystemPrompt
|
||||
{...rest}
|
||||
onAddLibrary={handleAddLibrary}
|
||||
libraries={libraries}
|
||||
placeholder={placeholder}
|
||||
inputParameters={inputParameters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
type GetLLMNodeFCSettingDetailResponse,
|
||||
WorkflowMode,
|
||||
} from '@coze-arch/idl/workflow_api';
|
||||
import { useCurrentEntity } from '@flowgram-adapter/free-layout-editor';
|
||||
import {
|
||||
type ILibraryList,
|
||||
type ILibraryItem,
|
||||
} from '@coze-common/editor-plugins/library-insert';
|
||||
|
||||
import { useGlobalState } from '@/hooks';
|
||||
|
||||
import { getSkillsQueryParams } from '../skills/utils';
|
||||
import { useQuerySettingDetail } from '../skills/use-query-setting-detail';
|
||||
import type { BoundKnowledgeItem, BoundSkills } from '../skills/types';
|
||||
|
||||
interface Props {
|
||||
fcParam?: BoundSkills;
|
||||
}
|
||||
|
||||
enum DatasetFormat {
|
||||
Doc = 0,
|
||||
Table = 1,
|
||||
Image = 2,
|
||||
Database = 3,
|
||||
}
|
||||
|
||||
const formatSkills2Libraries = (
|
||||
skillsDetail: GetLLMNodeFCSettingDetailResponse,
|
||||
fcParam?: BoundSkills,
|
||||
): ILibraryList => {
|
||||
const result: ILibraryList = [];
|
||||
|
||||
const plugins: ILibraryItem[] | undefined =
|
||||
fcParam?.pluginFCParam?.pluginList?.map(item => {
|
||||
const pluginDetail = skillsDetail?.plugin_detail_map?.[item.plugin_id];
|
||||
const apiDetail = skillsDetail?.plugin_api_detail_map?.[item.api_id];
|
||||
|
||||
return {
|
||||
type: 'plugin',
|
||||
id: item.plugin_id,
|
||||
icon_url: pluginDetail?.icon_url || '',
|
||||
name: apiDetail?.name || '',
|
||||
desc: apiDetail?.description || '',
|
||||
api_id: apiDetail?.id,
|
||||
};
|
||||
});
|
||||
|
||||
const workflows: ILibraryItem[] | undefined =
|
||||
fcParam?.workflowFCParam?.workflowList
|
||||
?.filter(item => {
|
||||
const detail = skillsDetail?.workflow_detail_map?.[item.workflow_id];
|
||||
return (
|
||||
detail?.flow_mode === WorkflowMode.Workflow ||
|
||||
detail?.flow_mode === WorkflowMode.ChatFlow
|
||||
);
|
||||
})
|
||||
?.map(item => {
|
||||
const detail = skillsDetail?.workflow_detail_map?.[item.workflow_id];
|
||||
|
||||
return {
|
||||
type: 'workflow',
|
||||
id: item.workflow_id,
|
||||
icon_url: detail?.icon_url || '',
|
||||
name: detail?.name || '',
|
||||
desc: detail?.description || '',
|
||||
};
|
||||
});
|
||||
|
||||
const imageflows: ILibraryItem[] | undefined =
|
||||
fcParam?.workflowFCParam?.workflowList
|
||||
?.filter(item => {
|
||||
const detail = skillsDetail?.workflow_detail_map?.[item.workflow_id];
|
||||
return detail?.flow_mode === WorkflowMode.Imageflow;
|
||||
})
|
||||
?.map(item => {
|
||||
const detail = skillsDetail?.workflow_detail_map?.[item.workflow_id];
|
||||
|
||||
return {
|
||||
type: 'imageflow',
|
||||
id: item.workflow_id,
|
||||
icon_url: detail?.icon_url || '',
|
||||
name: detail?.name || '',
|
||||
desc: detail?.description || '',
|
||||
};
|
||||
});
|
||||
|
||||
const genDatasetFilter =
|
||||
(target: DatasetFormat) => (item: BoundKnowledgeItem) => {
|
||||
const detail = skillsDetail?.dataset_detail_map?.[item.id];
|
||||
return detail?.format_type === target;
|
||||
};
|
||||
|
||||
const tables: ILibraryItem[] | undefined =
|
||||
fcParam?.knowledgeFCParam?.knowledgeList
|
||||
?.filter(genDatasetFilter(DatasetFormat.Table))
|
||||
?.map(item => {
|
||||
const detail = skillsDetail?.dataset_detail_map?.[item.id];
|
||||
|
||||
return {
|
||||
type: 'table',
|
||||
id: item.id,
|
||||
icon_url: detail?.icon_url || '',
|
||||
name: detail?.name || '',
|
||||
desc: '',
|
||||
};
|
||||
});
|
||||
|
||||
const images: ILibraryItem[] | undefined =
|
||||
fcParam?.knowledgeFCParam?.knowledgeList
|
||||
?.filter(genDatasetFilter(DatasetFormat.Image))
|
||||
?.map(item => {
|
||||
const detail = skillsDetail?.dataset_detail_map?.[item.id];
|
||||
|
||||
return {
|
||||
type: 'image',
|
||||
id: item.id,
|
||||
icon_url: detail?.icon_url || '',
|
||||
name: detail?.name || '',
|
||||
desc: '',
|
||||
};
|
||||
});
|
||||
|
||||
const docs: ILibraryItem[] | undefined =
|
||||
fcParam?.knowledgeFCParam?.knowledgeList
|
||||
?.filter(genDatasetFilter(DatasetFormat.Doc))
|
||||
?.map(item => {
|
||||
const detail = skillsDetail?.dataset_detail_map?.[item.id];
|
||||
|
||||
return {
|
||||
type: 'text',
|
||||
id: item.id,
|
||||
icon_url: detail?.icon_url || '',
|
||||
name: detail?.name || '',
|
||||
desc: '',
|
||||
};
|
||||
});
|
||||
|
||||
if (plugins?.length) {
|
||||
result.push({
|
||||
type: 'plugin',
|
||||
items: plugins,
|
||||
});
|
||||
}
|
||||
|
||||
if (workflows?.length) {
|
||||
result.push({
|
||||
type: 'workflow',
|
||||
items: workflows,
|
||||
});
|
||||
}
|
||||
|
||||
if (imageflows?.length) {
|
||||
result.push({
|
||||
type: 'imageflow',
|
||||
items: imageflows,
|
||||
});
|
||||
}
|
||||
|
||||
if (tables?.length) {
|
||||
result.push({
|
||||
type: 'table',
|
||||
items: tables,
|
||||
});
|
||||
}
|
||||
|
||||
if (images?.length) {
|
||||
result.push({
|
||||
type: 'image',
|
||||
items: images,
|
||||
});
|
||||
}
|
||||
|
||||
if (docs?.length) {
|
||||
result.push({
|
||||
type: 'text',
|
||||
items: docs,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export default function useSkillLibraries(props: Props) {
|
||||
const { fcParam = {} } = props;
|
||||
|
||||
const globalState = useGlobalState();
|
||||
const node = useCurrentEntity();
|
||||
|
||||
const { data: skillsDetail, refetch } = useQuerySettingDetail({
|
||||
workflowId: globalState.workflowId,
|
||||
spaceId: globalState.spaceId,
|
||||
nodeId: node.id,
|
||||
...getSkillsQueryParams(fcParam),
|
||||
});
|
||||
|
||||
const libraries = useMemo(
|
||||
() =>
|
||||
formatSkills2Libraries(
|
||||
skillsDetail as GetLLMNodeFCSettingDetailResponse,
|
||||
fcParam,
|
||||
),
|
||||
[skillsDetail, fcParam],
|
||||
);
|
||||
|
||||
return {
|
||||
libraries,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type BatchVO, type InputValueVO } from '@coze-workflow/base';
|
||||
|
||||
import type { IModelValue } from '@/typing';
|
||||
|
||||
export enum BatchMode {
|
||||
Single = 'single',
|
||||
Batch = 'batch',
|
||||
}
|
||||
|
||||
export interface FormData {
|
||||
batchMode: BatchMode;
|
||||
visionParam?: InputValueVO[];
|
||||
model?: IModelValue;
|
||||
$$input_decorator$$: {
|
||||
inputParameters?: InputValueVO[];
|
||||
chatHistorySetting?: {
|
||||
enableChatHistory?: boolean;
|
||||
chatHistoryRound?: number;
|
||||
};
|
||||
};
|
||||
batch: BatchVO;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useForm } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
|
||||
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
|
||||
import { ExpressionEditor } from '@/nodes-v2/components/expression-editor';
|
||||
import { useWorkflowModels } from '@/hooks';
|
||||
import { FormCard } from '@/form-extensions/components/form-card';
|
||||
import { CopyButton } from '@/components/copy-button';
|
||||
|
||||
export const UserPrompt = ({ field, fieldState }) => {
|
||||
const form = useForm();
|
||||
const readonly = useReadonly();
|
||||
const { models } = useWorkflowModels();
|
||||
|
||||
const modelType = form.getValueIn('model.modelType');
|
||||
const curModel = models?.find(model => model.model_type === modelType);
|
||||
const isUserPromptRequired = curModel?.is_up_required ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: 临时方案,待节点引擎提供新 api 后替换
|
||||
field._fieldModel.validate();
|
||||
}, [isUserPromptRequired]);
|
||||
|
||||
return (
|
||||
<FormCard
|
||||
key={'FormCard'}
|
||||
header={I18n.t('workflow_detail_llm_prompt')}
|
||||
tooltip={I18n.t('workflow_detail_llm_prompt_tooltip')}
|
||||
required={isUserPromptRequired}
|
||||
actionButton={
|
||||
readonly ? [<CopyButton value={field?.value as string} />] : []
|
||||
}
|
||||
>
|
||||
<ExpressionEditor
|
||||
placeholder={I18n.t('workflow_detail_llm_prompt_content')}
|
||||
maxLength={500}
|
||||
{...field}
|
||||
inputParameters={form.getValueIn('$$input_decorator$$.inputParameters')}
|
||||
key="ExpressionEditor"
|
||||
isError={!!fieldState?.errors?.length}
|
||||
/>
|
||||
<FormItemFeedback errors={fieldState?.errors} />
|
||||
</FormCard>
|
||||
);
|
||||
};
|
||||
197
frontend/packages/workflow/playground/src/nodes-v2/llm/utils.ts
Normal file
197
frontend/packages/workflow/playground/src/nodes-v2/llm/utils.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { camelCase } from 'lodash-es';
|
||||
import type {
|
||||
ILibraryItem,
|
||||
LibraryType,
|
||||
} from '@coze-common/editor-plugins/library-insert';
|
||||
import { DEFAULT_MODEL_TYPE } from '@coze-workflow/nodes';
|
||||
import {
|
||||
BlockInput,
|
||||
GenerationDiversity,
|
||||
VariableTypeDTO,
|
||||
type InputValueDTO,
|
||||
} from '@coze-workflow/base';
|
||||
import { ModelParamType, type Model } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { isDraftByProjectId } from '@/nodes-v2/llm/skills/utils';
|
||||
|
||||
import {
|
||||
type BoundKnowledgeItem,
|
||||
type BoundPluginItem,
|
||||
type BoundSkills,
|
||||
type BoundWorkflowItem,
|
||||
SkillType,
|
||||
} from './skills/types';
|
||||
import { defaultKnowledgeGlobalSetting } from './skills/constants';
|
||||
|
||||
const getDefaultModels = (modelMeta: Model): InputValueDTO[] => {
|
||||
const defaultModel: InputValueDTO[] = [];
|
||||
|
||||
modelMeta?.model_params?.forEach(p => {
|
||||
const k = camelCase(p.name) as string;
|
||||
const { type } = p;
|
||||
// 优先取平衡,自定义兜底
|
||||
const defaultValue =
|
||||
p.default_val[GenerationDiversity.Balance] ??
|
||||
p.default_val[GenerationDiversity.Customize];
|
||||
|
||||
if (defaultValue !== undefined) {
|
||||
if (ModelParamType.Float === type) {
|
||||
defaultModel.push(BlockInput.createFloat(k, defaultValue));
|
||||
} else if (ModelParamType.Int === type || ['modelType'].includes(k)) {
|
||||
defaultModel.push(BlockInput.createInteger(k, defaultValue));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return defaultModel;
|
||||
};
|
||||
|
||||
export const getDefaultLLMParams = (models: Model[]) => {
|
||||
const modelMeta =
|
||||
models.find(m => m.model_type === DEFAULT_MODEL_TYPE) ?? models[0];
|
||||
|
||||
const llmParam = [
|
||||
BlockInput.createInteger('modelType', `${modelMeta?.model_type ?? ''}`),
|
||||
BlockInput.createString('modelName', modelMeta?.name ?? ''),
|
||||
BlockInput.createString('generationDiversity', GenerationDiversity.Balance),
|
||||
...getDefaultModels(modelMeta),
|
||||
].filter(Boolean);
|
||||
|
||||
return llmParam;
|
||||
};
|
||||
|
||||
export const reviseLLMParamPair = (d: InputValueDTO): [string, unknown] => {
|
||||
let k = d?.name || '';
|
||||
|
||||
// TODO 前端不依赖这个字段,确认后端无依赖后,可删除
|
||||
// 兼容一个历史悠久的拼写错误
|
||||
if (k === 'modleName') {
|
||||
k = 'modelName';
|
||||
}
|
||||
let v = d.input.value.content;
|
||||
if (
|
||||
[VariableTypeDTO.float, VariableTypeDTO.integer].includes(
|
||||
d.input.type as VariableTypeDTO,
|
||||
)
|
||||
) {
|
||||
v = Number(d.input.value.content);
|
||||
}
|
||||
|
||||
return [k, v];
|
||||
};
|
||||
|
||||
export const modelItemToBlockInput = (
|
||||
model: Model,
|
||||
modelMeta: Model | undefined,
|
||||
): BlockInput[] =>
|
||||
Object.keys(model).map(k => {
|
||||
const type = modelMeta?.model_params?.find(
|
||||
p => camelCase(p.name) === k,
|
||||
)?.type;
|
||||
if (ModelParamType.Float === type) {
|
||||
return BlockInput.createFloat(k, model[k]);
|
||||
} else if (ModelParamType.Int === type || ['modelType'].includes(k)) {
|
||||
return BlockInput.createInteger(k, model[k]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let _k = k;
|
||||
// TODO 前端不依赖这个字段,确认后端无依赖后,可删除
|
||||
if (_k === 'modelName') {
|
||||
_k = 'modleName';
|
||||
}
|
||||
return BlockInput.createString(_k, model[k]);
|
||||
});
|
||||
|
||||
const libraryType2SkillsType = (type: LibraryType): SkillType => {
|
||||
if (type === 'plugin') {
|
||||
return SkillType.Plugin;
|
||||
}
|
||||
|
||||
if (type === 'imageflow' || type === 'workflow') {
|
||||
return SkillType.Workflow;
|
||||
}
|
||||
|
||||
return SkillType.Knowledge;
|
||||
};
|
||||
|
||||
export const addSKillFromLibrary = (
|
||||
library: ILibraryItem,
|
||||
_skills: BoundSkills,
|
||||
): BoundSkills => {
|
||||
const type = libraryType2SkillsType(library.type);
|
||||
const skills: BoundSkills = _skills || {};
|
||||
|
||||
if (type === SkillType.Plugin) {
|
||||
const data = skills.pluginFCParam?.pluginList || [];
|
||||
const detail = library.detail_info?.plugin_detail;
|
||||
data.push({
|
||||
plugin_id: detail?.plugin_id as string,
|
||||
api_id: detail?.api_id as string,
|
||||
api_name: detail?.name as string,
|
||||
plugin_version: '', // 和 @张友松 确认 不传版本
|
||||
is_draft: isDraftByProjectId(detail?.project_id),
|
||||
});
|
||||
|
||||
return {
|
||||
...skills,
|
||||
pluginFCParam: {
|
||||
pluginList: data as Array<BoundPluginItem>,
|
||||
},
|
||||
};
|
||||
} else if (type === SkillType.Workflow) {
|
||||
const data = skills.workflowFCParam?.workflowList || [];
|
||||
const detail = library.detail_info?.workflow_detail;
|
||||
data.push({
|
||||
plugin_id: detail?.plugin_id as string,
|
||||
workflow_id: detail?.workflow_id as string,
|
||||
plugin_version: '',
|
||||
workflow_version: '', // 和 @张友松 确认 不传版本
|
||||
is_draft: isDraftByProjectId(detail?.project_id),
|
||||
});
|
||||
|
||||
return {
|
||||
...skills,
|
||||
workflowFCParam: {
|
||||
workflowList: data as Array<BoundWorkflowItem>,
|
||||
},
|
||||
};
|
||||
} else if (type === SkillType.Knowledge) {
|
||||
const data = skills.knowledgeFCParam?.knowledgeList || [];
|
||||
const detail = library.detail_info?.knowledge_detail;
|
||||
|
||||
data.push({
|
||||
id: detail?.id as string,
|
||||
name: detail?.name as string,
|
||||
});
|
||||
|
||||
return {
|
||||
...skills,
|
||||
knowledgeFCParam: {
|
||||
...skills.knowledgeFCParam,
|
||||
knowledgeList: data as Array<BoundKnowledgeItem>,
|
||||
global_setting:
|
||||
skills.knowledgeFCParam?.global_setting ??
|
||||
defaultKnowledgeGlobalSetting,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return skills;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { llmOutputTreeMetaValidator } from './llm-output-tree-meta-validator';
|
||||
export { llmInputNameValidator } from './llm-input-name-validator';
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { nameValidationRule } from '@/nodes-v2/components/helpers';
|
||||
|
||||
import { isVisionEqual, isVisionInput } from '../vision';
|
||||
|
||||
export const llmInputNameValidator = ({ value, formValues, name }) => {
|
||||
const validatorRule = nameValidationRule;
|
||||
|
||||
/** 命名校验 */
|
||||
if (!validatorRule.test(value)) {
|
||||
return I18n.t('workflow_detail_node_error_format');
|
||||
}
|
||||
|
||||
const inputValues =
|
||||
get(formValues, '$$input_decorator$$.inputParameters') || [];
|
||||
const paths = name.split('.');
|
||||
paths.pop();
|
||||
const inputValue = get(formValues, paths);
|
||||
|
||||
if (!inputValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sameVisionInputs = inputValues.filter(
|
||||
item => item.name === value && isVisionEqual(item, inputValue),
|
||||
);
|
||||
|
||||
// 都是输入或者视觉理解的场景直接返回重名
|
||||
if (sameVisionInputs.length > 1) {
|
||||
return I18n.t('workflow_detail_node_input_duplicated');
|
||||
}
|
||||
|
||||
// 输入和视觉理解参数重名的场景,返回不能和视觉理解参数重名
|
||||
// 视觉理解参数和输入重名,返回不能和输入重名
|
||||
const differentVisionInputs = inputValues.filter(
|
||||
item => item.name === value && !isVisionEqual(item, inputValue),
|
||||
);
|
||||
if (differentVisionInputs.length > 0) {
|
||||
if (isVisionInput(inputValue)) {
|
||||
return I18n.t('workflow_250317_01', undefined, '不能和输入重名');
|
||||
} else {
|
||||
return I18n.t('workflow_250317_02', undefined, '不能和视觉理解输入重名');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { type ZodError } from 'zod';
|
||||
import { get } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type Validate } from '@flowgram-adapter/free-layout-editor';
|
||||
|
||||
import { omitSystemReasoningContent, REASONING_CONTENT_NAME } from '../cot';
|
||||
|
||||
/** 变量命名校验规则 */
|
||||
const outputTreeValidationRule =
|
||||
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/;
|
||||
|
||||
/** 校验逻辑 */
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const OutputTreeMetaSchema = z.lazy(() =>
|
||||
z
|
||||
.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: I18n.t('workflow_detail_node_error_name_empty'),
|
||||
})
|
||||
.min(1, I18n.t('workflow_detail_node_error_name_empty'))
|
||||
.regex(
|
||||
outputTreeValidationRule,
|
||||
I18n.t('workflow_detail_node_error_format'),
|
||||
)
|
||||
.refine(
|
||||
val => {
|
||||
if (val === REASONING_CONTENT_NAME) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: I18n.t('workflow_250213_01'),
|
||||
},
|
||||
),
|
||||
children: z.array(OutputTreeMetaSchema).optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
);
|
||||
|
||||
const omitErrorBody = (value, isBatch) => {
|
||||
// 批量,去除 children 中的 errorBody
|
||||
if (isBatch) {
|
||||
return value?.map(v => ({
|
||||
...v,
|
||||
children: v?.children?.filter(c => c?.name !== 'errorBody'),
|
||||
}));
|
||||
}
|
||||
// 单次,去除 value 中的 errorBody
|
||||
return value?.filter(v => v?.name !== 'errorBody');
|
||||
};
|
||||
|
||||
export const llmOutputTreeMetaValidator: Validate = ({ value, formValues }) => {
|
||||
/**
|
||||
* 判断错误异常处理是否打开,如果打开需要过滤掉 errorBody 后做校验
|
||||
*/
|
||||
const { settingOnErrorIsOpen = false } = (get(formValues, 'settingOnError') ??
|
||||
{}) as { settingOnErrorIsOpen?: boolean };
|
||||
|
||||
/**
|
||||
* 根据 batch 数据判断,当前是否处于批处理状态
|
||||
*/
|
||||
const batchValue = get(formValues, 'batchMode');
|
||||
const isBatch = batchValue === 'batch';
|
||||
const reasoningContentOmittedValue = omitSystemReasoningContent(
|
||||
value,
|
||||
isBatch,
|
||||
);
|
||||
|
||||
const parsed = z
|
||||
.array(OutputTreeMetaSchema)
|
||||
.safeParse(
|
||||
settingOnErrorIsOpen
|
||||
? omitErrorBody(reasoningContentOmittedValue, isBatch)
|
||||
: reasoningContentOmittedValue,
|
||||
);
|
||||
|
||||
if (!parsed.success) {
|
||||
const errorText = JSON.stringify((parsed as { error: ZodError }).error);
|
||||
return errorText;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.vision-add-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.vision-title{
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.vision-name-container {
|
||||
:global {
|
||||
.semi-input-wrapper__with-prefix {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import {
|
||||
type FieldRenderProps,
|
||||
type FieldArrayRenderProps,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type InputValueVO, type ViewVariableType } from '@coze-workflow/base';
|
||||
import { IconCozMinus } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
|
||||
import { isVisionInput } from '../utils/index';
|
||||
import { VisionValueField } from './vision-value-field';
|
||||
import { VisionNameField } from './vision-name-field';
|
||||
|
||||
interface VisionInputFieldProps {
|
||||
inputField: FieldRenderProps<InputValueVO>['field'];
|
||||
inputsField: FieldArrayRenderProps<InputValueVO>['field'];
|
||||
index: number;
|
||||
readonly?: boolean;
|
||||
form;
|
||||
enabledTypes: ViewVariableType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入字段
|
||||
*/
|
||||
export const VisionInputField: FC<VisionInputFieldProps> = ({
|
||||
readonly,
|
||||
inputField,
|
||||
inputsField,
|
||||
index,
|
||||
enabledTypes,
|
||||
}) => {
|
||||
if (!isVisionInput(inputField?.value)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={'flex items-start pb-1 gap-1'}>
|
||||
<VisionNameField
|
||||
inputField={inputField}
|
||||
inputsField={inputsField}
|
||||
enabledTypes={enabledTypes}
|
||||
/>
|
||||
<VisionValueField
|
||||
name={`${inputField.name}.input`}
|
||||
enabledTypes={enabledTypes}
|
||||
/>
|
||||
{readonly ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="leading-none">
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={<IconCozMinus className="text-sm" />}
|
||||
onClick={() => {
|
||||
inputsField.delete(index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMemo, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Field,
|
||||
useForm,
|
||||
type FieldArrayRenderProps,
|
||||
type FieldRenderProps,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { ViewVariableType, type InputValueVO } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { NodeInputName } from '@/nodes-v2/components/node-input-name';
|
||||
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface VisionNameFieldProps {
|
||||
inputField: FieldRenderProps<InputValueVO>['field'];
|
||||
inputsField: FieldArrayRenderProps<InputValueVO>['field'];
|
||||
enabledTypes: ViewVariableType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入名称字段
|
||||
*/
|
||||
export const VisionNameField: FC<VisionNameFieldProps> = ({
|
||||
inputField,
|
||||
inputsField,
|
||||
enabledTypes,
|
||||
}) => {
|
||||
const form = useForm();
|
||||
const input = form.getValueIn(`${inputField.name}.input`);
|
||||
|
||||
const disabledTooltip = useMemo(() => {
|
||||
if (!enabledTypes.length) {
|
||||
return I18n.t('workflow_250310_05', undefined, '所选模型不支持视觉理解');
|
||||
}
|
||||
|
||||
const type = input?.rawMeta?.type;
|
||||
if (!type || [...enabledTypes, ViewVariableType.String].includes(type)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return type === ViewVariableType.Image
|
||||
? I18n.t('workflow_250320_01', undefined, '所选模型不支持图片理解')
|
||||
: I18n.t('workflow_250320_02', undefined, '所选模型不支持视频理解');
|
||||
}, [enabledTypes, input?.rawMeta?.type]);
|
||||
|
||||
return (
|
||||
<Field name={`${inputField.name}.name`}>
|
||||
{({
|
||||
field: childNameField,
|
||||
fieldState: nameFieldState,
|
||||
}: FieldRenderProps<string>) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex-[2] min-w-0 relative',
|
||||
styles['vision-name-container'],
|
||||
)}
|
||||
>
|
||||
<NodeInputName
|
||||
{...childNameField}
|
||||
input={input}
|
||||
inputParameters={inputsField.value || []}
|
||||
isError={!!nameFieldState?.errors?.length}
|
||||
inputPrefix={
|
||||
disabledTooltip ? (
|
||||
<Tooltip content={disabledTooltip}>
|
||||
<IconCozInfoCircle className="coz-fg-hglt-yellow cursor-pointer text-sm" />
|
||||
</Tooltip>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<FormItemFeedback errors={nameFieldState?.errors} />
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import {
|
||||
Field,
|
||||
type FieldRenderProps,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type ValueExpression, ViewVariableType } from '@coze-workflow/base';
|
||||
|
||||
import { ValueExpressionInput } from '@/nodes-v2/components/value-expression-input';
|
||||
import { FormItemFeedback } from '@/nodes-v2/components/form-item-feedback';
|
||||
|
||||
import { DEFUALT_VISION_INPUT } from '../constants';
|
||||
|
||||
interface VisionProps {
|
||||
name: string;
|
||||
enabledTypes: ViewVariableType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入值字段
|
||||
* @returns */
|
||||
export const VisionValueField: FC<VisionProps> = ({ enabledTypes, name }) => {
|
||||
const disabledTypes = ViewVariableType.getComplement([
|
||||
...enabledTypes,
|
||||
ViewVariableType.String,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Field name={name}>
|
||||
{({
|
||||
field: childInputField,
|
||||
fieldState: inputFieldState,
|
||||
}: FieldRenderProps<ValueExpression | undefined>) => (
|
||||
<div className="flex-[3] min-w-0">
|
||||
<ValueExpressionInput
|
||||
{...childInputField}
|
||||
isError={!!inputFieldState?.errors?.length}
|
||||
disabledTypes={disabledTypes}
|
||||
defaultInputType={enabledTypes[0]}
|
||||
inputTypes={enabledTypes}
|
||||
onChange={v => {
|
||||
const expression = v as ValueExpression;
|
||||
if (!expression) {
|
||||
// 默认值需要带raw meta不然无法区分是不是视觉理解
|
||||
childInputField?.onChange(DEFUALT_VISION_INPUT);
|
||||
return;
|
||||
}
|
||||
const newExpression: ValueExpression = {
|
||||
...expression,
|
||||
rawMeta: {
|
||||
...(expression.rawMeta || {}),
|
||||
isVision: true,
|
||||
},
|
||||
};
|
||||
childInputField?.onChange(newExpression);
|
||||
}}
|
||||
/>
|
||||
<FormItemFeedback errors={inputFieldState?.errors} />
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type FC } from 'react';
|
||||
|
||||
import {
|
||||
FieldArray,
|
||||
useForm,
|
||||
type FieldArrayRenderProps,
|
||||
} from '@flowgram-adapter/free-layout-editor';
|
||||
import { type InputValueVO, ValueExpressionType } from '@coze-workflow/base';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
|
||||
import { useReadonly } from '@/nodes-v2/hooks/use-readonly';
|
||||
import { AddIcon } from '@/nodes-v2/components/add-icon';
|
||||
import { FormCard } from '@/form-extensions/components/form-card';
|
||||
import { ColumnsTitleWithAction } from '@/form-extensions/components/columns-title-with-action';
|
||||
|
||||
import { useModelEnabledTypes } from '../hooks/use-model-enabled-types';
|
||||
import { VisionInputField } from './vision-input-field';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface VisionProps {
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视觉理解配置
|
||||
*/
|
||||
export const Vision: FC<VisionProps> = () => {
|
||||
const enabledTypes = useModelEnabledTypes();
|
||||
const disabledTooltip = !enabledTypes.length
|
||||
? I18n.t('workflow_250310_05', undefined, '所选模型不支持视觉理解')
|
||||
: '';
|
||||
const form = useForm();
|
||||
const readonly = useReadonly();
|
||||
|
||||
return (
|
||||
<FieldArray name={'$$input_decorator$$.inputParameters'}>
|
||||
{({ field }: FieldArrayRenderProps<InputValueVO>) => (
|
||||
<FormCard
|
||||
header={I18n.t('workflow_250310_04', undefined, '视觉理解输入')}
|
||||
tooltip={I18n.t(
|
||||
'workflow_250320_03',
|
||||
undefined,
|
||||
'用于视觉理解的输入,传入图片or视频的url;并在Prompt中应用该输入',
|
||||
)}
|
||||
>
|
||||
<div className={styles['vision-title']}>
|
||||
<ColumnsTitleWithAction
|
||||
columns={[
|
||||
{
|
||||
title: I18n.t('workflow_detail_variable_input_name'),
|
||||
style: {
|
||||
flex: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: I18n.t('workflow_detail_variable_input_value'),
|
||||
style: {
|
||||
flex: 3,
|
||||
},
|
||||
},
|
||||
]}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>
|
||||
{field.map((child, index) => (
|
||||
<VisionInputField
|
||||
inputField={child}
|
||||
inputsField={field}
|
||||
index={index}
|
||||
readonly={readonly}
|
||||
form={form}
|
||||
enabledTypes={enabledTypes}
|
||||
key={child.key}
|
||||
></VisionInputField>
|
||||
))}
|
||||
{readonly ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className={styles['vision-add-icon']}>
|
||||
<AddIcon
|
||||
disabledTooltip={disabledTooltip}
|
||||
onClick={() => {
|
||||
field.append({
|
||||
name: '',
|
||||
input: {
|
||||
type: ValueExpressionType.REF,
|
||||
rawMeta: { isVision: true },
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FormCard>
|
||||
)}
|
||||
</FieldArray>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type ValueExpression, ValueExpressionType } from '@coze-workflow/base';
|
||||
|
||||
export const DEFUALT_VISION_INPUT: ValueExpression = {
|
||||
type: ValueExpressionType.REF,
|
||||
rawMeta: { isVision: true },
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useService } from '@flowgram-adapter/free-layout-editor';
|
||||
import { ViewVariableType } from '@coze-workflow/base';
|
||||
|
||||
import { WorkflowModelsService } from '@/services';
|
||||
|
||||
import { useModelType } from '../../hooks/use-model-type';
|
||||
|
||||
/**
|
||||
* 模型支持的数据类型
|
||||
*/
|
||||
export function useModelEnabledTypes() {
|
||||
const modelType = useModelType();
|
||||
const modelsService = useService(WorkflowModelsService);
|
||||
const modelAbility = modelsService.getModelAbility(modelType);
|
||||
const enabledTypes: ViewVariableType[] = [];
|
||||
|
||||
if (modelAbility?.image_understanding) {
|
||||
enabledTypes.push(ViewVariableType.Image);
|
||||
}
|
||||
|
||||
if (modelAbility?.video_understanding) {
|
||||
enabledTypes.push(ViewVariableType.Video);
|
||||
}
|
||||
|
||||
return enabledTypes;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Vision } from './components/vision';
|
||||
export { isVisionInput, isVisionEqual } from './utils';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { isVisionInput } from './is-vision-input';
|
||||
export { isVisionEqual } from './is-vision-equal';
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type InputValueVO } from '@coze-workflow/base';
|
||||
|
||||
import { isVisionInput } from './is-vision-input';
|
||||
|
||||
/**
|
||||
* 判断是否是相同的输入类型
|
||||
* @param value1
|
||||
* @param value2
|
||||
* @returns
|
||||
*/
|
||||
export const isVisionEqual = (
|
||||
value1: InputValueVO,
|
||||
value2: InputValueVO,
|
||||
): boolean => isVisionInput(value1) === isVisionInput(value2);
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type InputValueVO } from '@coze-workflow/base';
|
||||
|
||||
/**
|
||||
* 是不是视觉理解的输入
|
||||
*/
|
||||
export const isVisionInput = (value: InputValueVO): boolean =>
|
||||
!!value?.input?.rawMeta?.isVision;
|
||||
Reference in New Issue
Block a user