Compare commits

...

1 Commits

Author SHA1 Message Date
zhsama
a06fc6079e feat(workflow): add multi-turn context code generator modal 2026-01-20 18:04:48 +08:00
8 changed files with 719 additions and 11 deletions

View File

@@ -4,13 +4,16 @@ import type { CodeLanguage } from '../../code/types'
import type { GenRes } from '@/service/debug'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback } from 'react'
import { useCallback, useMemo } from 'react'
import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res'
import { ActionButton } from '@/app/components/base/action-button'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { AppModeEnum } from '@/types/app'
import { cn } from '@/utils/classnames'
import { useHooksStore } from '../../../hooks-store'
import { useStore } from '../../../store'
import { BlockEnum } from '../../../types'
import ContextGenerateModal from '../../tool/components/context-generate-modal'
type Props = {
nodeId: string
@@ -28,12 +31,39 @@ const CodeGenerateBtn: FC<Props> = ({
onGenerated,
}) => {
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const nodes = useStore(s => s.nodes)
const handleAutomaticRes = useCallback((res: GenRes) => {
onGenerated?.(res.modified)
showAutomaticFalse()
}, [onGenerated, showAutomaticFalse])
const configsMap = useHooksStore(s => s.configsMap)
const parseExtractorNodeId = useCallback((id: string) => {
const marker = '_ext_'
const index = id.lastIndexOf(marker)
if (index < 0)
return null
const parentId = id.slice(0, index)
const paramKey = id.slice(index + marker.length)
if (!parentId || !paramKey)
return null
return { parentId, paramKey }
}, [])
const contextGenerateConfig = useMemo(() => {
const targetNode = nodes.find(node => node.id === nodeId)
const isCodeNode = targetNode?.data?.type === BlockEnum.Code
const parentNodeId = (targetNode?.data as { parent_node_id?: string })?.parent_node_id
const parsed = parseExtractorNodeId(nodeId)
if (!isCodeNode || !parentNodeId || !parsed?.paramKey)
return null
return {
toolNodeId: parentNodeId || parsed.parentId,
paramKey: parsed.paramKey,
codeNodeId: nodeId,
}
}, [nodeId, nodes, parseExtractorNodeId])
return (
<div className={cn(className)}>
<ActionButton
@@ -43,16 +73,28 @@ const CodeGenerateBtn: FC<Props> = ({
<Generator className="h-4 w-4 text-primary-600" />
</ActionButton>
{showAutomatic && (
<GetCodeGeneratorResModal
mode={AppModeEnum.CHAT}
isShow={showAutomatic}
codeLanguages={codeLanguages}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentCode={currentCode}
/>
contextGenerateConfig
? (
<ContextGenerateModal
isShow={showAutomatic}
onClose={showAutomaticFalse}
toolNodeId={contextGenerateConfig.toolNodeId}
paramKey={contextGenerateConfig.paramKey}
codeNodeId={contextGenerateConfig.codeNodeId}
/>
)
: (
<GetCodeGeneratorResModal
mode={AppModeEnum.CHAT}
isShow={showAutomatic}
codeLanguages={codeLanguages}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
flowId={configsMap?.flowId || ''}
nodeId={nodeId}
currentCode={currentCode}
/>
)
)}
</div>
)

View File

@@ -0,0 +1,538 @@
'use client'
import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
import type { OutputVar } from '@/app/components/workflow/nodes/code/types'
import type { ContextGenerateMessage, ContextGenerateResponse } from '@/service/debug'
import type { AppModeEnum, CompletionParams, Model, ModelModeType } from '@/types/app'
import {
RiSendPlaneLine,
} from '@remixicon/react'
import { useSessionStorageState } from 'ahooks'
import useBoolean from 'ahooks/lib/useBoolean'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import Loading from '@/app/components/base/loading'
import Modal from '@/app/components/base/modal'
import Toast from '@/app/components/base/toast'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import VersionSelector from '@/app/components/app/configuration/config/automatic/version-selector'
import ResPlaceholder from '@/app/components/app/configuration/config/automatic/res-placeholder'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { NodeRunningStatus, VarType } from '@/app/components/workflow/types'
import { generateContext } from '@/service/debug'
import { cn } from '@/utils/classnames'
import useContextGenData from './use-context-gen-data'
type Props = {
isShow: boolean
onClose: () => void
toolNodeId: string
paramKey: string
codeNodeId: string
}
const minCodeHeight = 220
const minOutputHeight = 160
const splitHandleHeight = 6
const normalizeCodeLanguage = (value?: string) => {
if (value === CodeLanguage.javascript)
return CodeLanguage.javascript
if (value === CodeLanguage.python3)
return CodeLanguage.python3
return CodeLanguage.python3
}
const normalizeOutputs = (outputs?: Record<string, { type: string }>) => {
const next: OutputVar = {}
Object.entries(outputs || {}).forEach(([key, value]) => {
const type = Object.values(VarType).includes(value?.type as VarType)
? value.type as VarType
: VarType.string
next[key] = {
type,
children: null,
}
})
return next
}
const mapOutputsToResponse = (outputs?: OutputVar) => {
const next: Record<string, { type: string }> = {}
Object.entries(outputs || {}).forEach(([key, value]) => {
next[key] = { type: value.type }
})
return next
}
const ContextGenerateModal: FC<Props> = ({
isShow,
onClose,
toolNodeId,
paramKey,
codeNodeId,
}) => {
const { t } = useTranslation()
const configsMap = useHooksStore(s => s.configsMap)
const nodes = useStore(s => s.nodes)
const workflowStore = useWorkflowStore()
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
const flowId = configsMap?.flowId || ''
const storageKey = useMemo(() => {
const segments = [flowId || 'unknown', toolNodeId, paramKey].filter(Boolean)
return segments.join('-')
}, [flowId, paramKey, toolNodeId])
const codeNode = useMemo(() => {
return nodes.find(node => node.id === codeNodeId)
}, [codeNodeId, nodes])
const codeNodeData = codeNode?.data as CodeNodeType | undefined
const fallbackVersion = useMemo<ContextGenerateResponse | null>(() => {
if (!codeNodeData)
return null
return {
variables: (codeNodeData.variables || []).map(variable => ({
variable: variable.variable,
value_selector: Array.isArray(variable.value_selector) ? variable.value_selector : [],
})),
code_language: codeNodeData.code_language,
code: codeNodeData.code || '',
outputs: mapOutputsToResponse(codeNodeData.outputs),
message: '',
error: '',
}
}, [codeNodeData])
const {
versions,
addVersion,
current,
currentVersionIndex,
setCurrentVersionIndex,
} = useContextGenData({
storageKey,
})
const [promptMessages, setPromptMessages] = useSessionStorageState<ContextGenerateMessage[]>(
`${storageKey}-messages`,
{ defaultValue: [] },
)
const [inputValue, setInputValue] = useState('')
const [isGenerating, { setTrue: setGeneratingTrue, setFalse: setGeneratingFalse }] = useBoolean(false)
const defaultCompletionParams = {
temperature: 0.7,
max_tokens: 0,
top_p: 0,
echo: false,
stop: [],
presence_penalty: 0,
frequency_penalty: 0,
}
const localModel = localStorage.getItem('auto-gen-model')
? JSON.parse(localStorage.getItem('auto-gen-model') as string) as Model
: null
const [model, setModel] = React.useState<Model>(localModel || {
name: '',
provider: '',
mode: AppModeEnum.CHAT as unknown as ModelModeType.chat,
completion_params: defaultCompletionParams,
})
const {
defaultModel,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
useEffect(() => {
if (defaultModel) {
const localModel = localStorage.getItem('auto-gen-model')
? JSON.parse(localStorage.getItem('auto-gen-model') || '')
: null
if (localModel) {
setModel({
...localModel,
completion_params: {
...defaultCompletionParams,
...localModel.completion_params,
},
})
}
else {
setModel(prev => ({
...prev,
name: defaultModel.model,
provider: defaultModel.provider.provider,
}))
}
}
}, [defaultModel])
const handleModelChange = useCallback((newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => {
const newModel = {
...model,
provider: newValue.provider,
name: newValue.modelId,
mode: newValue.mode as ModelModeType,
}
setModel(newModel)
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
}, [model])
const handleCompletionParamsChange = useCallback((newParams: FormValue) => {
const newModel = {
...model,
completion_params: newParams as CompletionParams,
}
setModel(newModel)
localStorage.setItem('auto-gen-model', JSON.stringify(newModel))
}, [model])
const chatListRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!chatListRef.current)
return
chatListRef.current.scrollTop = chatListRef.current.scrollHeight
}, [promptMessages, isGenerating])
const handleGenerate = useCallback(async () => {
const trimmed = inputValue.trim()
if (!trimmed || isGenerating)
return
if (!flowId || !toolNodeId || !paramKey)
return
const nextMessages = [...(promptMessages || []), { role: 'user', content: trimmed }]
setPromptMessages(nextMessages)
setInputValue('')
setGeneratingTrue()
try {
const response = await generateContext({
workflow_id: flowId,
node_id: toolNodeId,
parameter_name: paramKey,
language: normalizeCodeLanguage(current?.code_language || codeNodeData?.code_language) as 'python3' | 'javascript',
prompt_messages: nextMessages,
model_config: {
provider: model.provider,
name: model.name,
completion_params: model.completion_params,
},
})
if (response.error) {
Toast.notify({
type: 'error',
message: response.error,
})
return
}
const assistantMessage = response.message || t('nodes.tool.contextGenerate.defaultAssistantMessage', { ns: 'workflow' })
setPromptMessages([...nextMessages, { role: 'assistant', content: assistantMessage }])
addVersion(response)
}
finally {
setGeneratingFalse()
}
}, [
addVersion,
codeNodeData?.code_language,
current?.code_language,
flowId,
inputValue,
isGenerating,
model.completion_params,
model.name,
model.provider,
paramKey,
promptMessages,
setPromptMessages,
setGeneratingFalse,
setGeneratingTrue,
t,
toolNodeId,
])
const displayVersion = current || fallbackVersion
const displayCodeLanguage = normalizeCodeLanguage(displayVersion?.code_language)
const displayOutputData = useMemo(() => {
if (!displayVersion)
return {}
return {
variables: displayVersion.variables,
outputs: displayVersion.outputs,
}
}, [displayVersion])
const applyToNode = useCallback((closeOnApply: boolean) => {
if (!current || !codeNodeData)
return
const nextOutputs = normalizeOutputs(current.outputs)
const nextVariables = current.variables.map(item => ({
variable: item.variable,
value_selector: Array.isArray(item.value_selector) ? item.value_selector : [],
}))
handleNodeDataUpdateWithSyncDraft({
id: codeNodeId,
data: {
...codeNodeData,
code_language: normalizeCodeLanguage(current.code_language),
code: current.code,
outputs: nextOutputs,
variables: nextVariables,
},
})
if (closeOnApply)
onClose()
}, [codeNodeData, codeNodeId, current, handleNodeDataUpdateWithSyncDraft, onClose])
const handleRun = useCallback(() => {
if (!codeNodeId)
return
if (current)
applyToNode(false)
const store = workflowStore.getState()
store.setInitShowLastRunTab(true)
store.setPendingSingleRun({
nodeId: codeNodeId,
action: 'run',
})
}, [applyToNode, codeNodeId, current, workflowStore])
const isRunning = useMemo(() => {
const target = nodes.find(node => node.id === codeNodeId)
return target?.data?._singleRunningStatus === NodeRunningStatus.Running
}, [codeNodeId, nodes])
const rightContainerRef = useRef<HTMLDivElement>(null)
const [codePanelHeight, setCodePanelHeight] = useState(360)
const draggingRef = useRef(false)
const dragStartRef = useRef({ startY: 0, startHeight: 0 })
const handleResizeStart = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
draggingRef.current = true
dragStartRef.current = {
startY: event.clientY,
startHeight: codePanelHeight,
}
document.body.style.userSelect = 'none'
}, [codePanelHeight])
useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
if (!draggingRef.current)
return
const containerHeight = rightContainerRef.current?.offsetHeight || 0
const maxHeight = Math.max(minCodeHeight, containerHeight - minOutputHeight - splitHandleHeight)
const delta = event.clientY - dragStartRef.current.startY
const nextHeight = Math.min(Math.max(dragStartRef.current.startHeight + delta, minCodeHeight), maxHeight)
setCodePanelHeight(nextHeight)
}
const handleMouseUp = () => {
if (draggingRef.current) {
draggingRef.current = false
document.body.style.userSelect = ''
}
}
window.addEventListener('mousemove', handleMouseMove)
window.addEventListener('mouseup', handleMouseUp)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
window.removeEventListener('mouseup', handleMouseUp)
}
}, [])
const canRun = !!displayVersion?.code || !!codeNodeData?.code
return (
<Modal
isShow={isShow}
onClose={onClose}
className="min-w-[1140px] !p-0"
>
<div className="relative flex h-[680px] flex-wrap">
<div className="flex h-full w-[420px] shrink-0 flex-col border-r border-divider-regular p-6">
<div className="mb-4 text-lg font-bold leading-[28px] text-text-primary">
{t('nodes.tool.contextGenerate.title', { ns: 'workflow' })}
</div>
<div className="mb-4">
<ModelParameterModal
popupClassName="!w-[520px]"
portalToFollowElemContentClassName="z-[1000]"
isAdvancedMode={true}
provider={model.provider}
completionParams={model.completion_params}
modelId={model.name}
setModel={handleModelChange}
onCompletionParamsChange={handleCompletionParamsChange}
hideDebugWithMultipleModel
/>
</div>
<div
ref={chatListRef}
className="flex-1 space-y-2 overflow-y-auto pr-1"
>
{(promptMessages || []).map((message, index) => {
const isUser = message.role === 'user'
return (
<div
key={`${message.role}-${index}`}
className={cn('flex', isUser ? 'justify-end' : 'justify-start')}
>
<div
className={cn(
'max-w-[320px] whitespace-pre-wrap rounded-2xl px-4 py-3 text-sm',
isUser
? 'bg-background-gradient-bg-fill-chat-bubble-bg-3 text-text-primary'
: 'bg-chat-bubble-bg text-text-primary',
)}
>
{message.content}
</div>
</div>
)
})}
{isGenerating && (
<div className="flex justify-start">
<div className="flex items-center gap-2 rounded-2xl bg-chat-bubble-bg px-4 py-3 text-sm text-text-primary">
<LoadingAnim type="text" />
<span>{t('nodes.tool.contextGenerate.generating', { ns: 'workflow' })}</span>
</div>
</div>
)}
</div>
<div className="mt-4 flex items-center gap-2">
<Input
value={inputValue}
onChange={e => setInputValue(e.target.value)}
onKeyDown={(event) => {
if (event.key === 'Enter')
handleGenerate()
}}
placeholder={t('nodes.tool.contextGenerate.inputPlaceholder', { ns: 'workflow' }) as string}
disabled={isGenerating}
/>
<Button
variant="primary"
className="shrink-0 px-3"
disabled={!inputValue.trim() || isGenerating}
onClick={handleGenerate}
>
<RiSendPlaneLine className="h-4 w-4" />
</Button>
</div>
</div>
<div className="flex h-full w-0 grow flex-col bg-background-default-subtle p-6 pb-0">
<div className="mb-3 flex shrink-0 items-center justify-between">
<div>
<div className="text-base font-semibold leading-[160%] text-text-secondary">
{t('nodes.tool.contextGenerate.codeBlock', { ns: 'workflow' })}
</div>
{versions.length > 0 && (
<VersionSelector
versionLen={versions.length}
value={currentVersionIndex || 0}
onChange={setCurrentVersionIndex}
/>
)}
</div>
<div className="flex items-center gap-2">
<Button
onClick={handleRun}
disabled={!canRun || isGenerating || isRunning}
>
{t('nodes.tool.contextGenerate.run', { ns: 'workflow' })}
</Button>
<Button
variant="primary"
onClick={() => applyToNode(true)}
disabled={!current || isGenerating}
>
{t('nodes.tool.contextGenerate.apply', { ns: 'workflow' })}
</Button>
</div>
</div>
<div ref={rightContainerRef} className="flex h-full flex-col overflow-hidden">
{isGenerating && !displayVersion && (
<div className="flex h-full flex-col items-center justify-center space-y-3">
<Loading />
<div className="text-[13px] text-text-tertiary">
{t('nodes.tool.contextGenerate.generating', { ns: 'workflow' })}
</div>
</div>
)}
{!isGenerating && !displayVersion && (
<ResPlaceholder />
)}
{displayVersion && (
<div className="flex h-full flex-col overflow-hidden">
<div
className="flex min-h-[220px] flex-col overflow-hidden rounded-lg border border-components-panel-border bg-components-panel-bg"
style={{ height: codePanelHeight }}
>
<div className="px-3 pb-1 pt-2 text-xs font-semibold uppercase text-text-tertiary">
{t('nodes.tool.contextGenerate.code', { ns: 'workflow' })}
</div>
<div className="flex-1 overflow-hidden px-3 pb-3">
<CodeEditor
noWrapper
isExpand
readOnly
language={displayCodeLanguage}
value={displayVersion.code || ''}
className="h-full"
/>
</div>
</div>
<div
className="flex h-[6px] cursor-row-resize items-center justify-center"
onMouseDown={handleResizeStart}
>
<div className="h-1 w-8 rounded-full bg-divider-subtle" />
</div>
<div className="flex min-h-[160px] flex-1 flex-col overflow-hidden rounded-lg border border-components-panel-border bg-components-panel-bg">
<div className="px-3 pb-1 pt-2 text-xs font-semibold uppercase text-text-tertiary">
{t('nodes.tool.contextGenerate.output', { ns: 'workflow' })}
</div>
<div className="flex-1 overflow-hidden px-3 pb-3">
<CodeEditor
noWrapper
isExpand
readOnly
isJSONStringifyBeauty
language={CodeLanguage.json}
value={displayOutputData}
className="h-full"
/>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</Modal>
)
}
export default React.memo(ContextGenerateModal)

View File

@@ -0,0 +1,38 @@
import type { ContextGenerateResponse } from '@/service/debug'
import { useSessionStorageState } from 'ahooks'
import { useCallback } from 'react'
type Params = {
storageKey: string
}
const keyPrefix = 'context-gen-'
const useContextGenData = ({ storageKey }: Params) => {
const [versions, setVersions] = useSessionStorageState<ContextGenerateResponse[]>(`${keyPrefix}${storageKey}-versions`, {
defaultValue: [],
})
const [currentVersionIndex, setCurrentVersionIndex] = useSessionStorageState<number>(`${keyPrefix}${storageKey}-version-index`, {
defaultValue: 0,
})
const current = versions?.[currentVersionIndex || 0]
const addVersion = useCallback((version: ContextGenerateResponse) => {
setCurrentVersionIndex(() => versions?.length || 0)
setVersions((prev) => {
return [...(prev || []), version]
})
}, [setCurrentVersionIndex, setVersions, versions?.length])
return {
versions,
addVersion,
currentVersionIndex,
setCurrentVersionIndex,
current,
}
}
export default useContextGenData

View File

@@ -33,6 +33,7 @@ import { cn } from '@/utils/classnames'
import SubGraphModal from '../sub-graph-modal'
import AgentHeaderBar from './agent-header-bar'
import Placeholder from './placeholder'
import ContextGenerateModal from '../context-generate-modal'
/**
* Matches agent context variable syntax: {{@nodeId.context@}}
@@ -165,6 +166,7 @@ const MixedVariableTextInput = ({
const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const [isSubGraphModalOpen, setIsSubGraphModalOpen] = useState(false)
const [isContextGenerateModalOpen, setIsContextGenerateModalOpen] = useState(false)
const nodesByIdMap = useMemo(() => {
return availableNodes.reduce((acc, node) => {
@@ -517,6 +519,7 @@ const MixedVariableTextInput = ({
ensureAssembleExtractorNode()
onChange?.(assemblePlaceholder, VarKindTypeEnum.mixed, null)
setControlPromptEditorRerenderKey(Date.now())
setIsContextGenerateModalOpen(true)
return [extractorNodeId, 'result']
}, [assembleExtractorNodeId, assemblePlaceholder, ensureAssembleExtractorNode, onChange, paramKey, setControlPromptEditorRerenderKey, toolNodeId])
@@ -537,6 +540,10 @@ const MixedVariableTextInput = ({
setIsSubGraphModalOpen(false)
}, [])
const handleCloseContextGenerateModal = useCallback(() => {
setIsContextGenerateModalOpen(false)
}, [])
const sourceVariable: ValueSelector | undefined = detectedAgentFromValue
? [detectedAgentFromValue.nodeId, 'context']
: undefined
@@ -610,6 +617,15 @@ const MixedVariableTextInput = ({
agentNodeId={detectedAgentFromValue.nodeId}
/>
)}
{toolNodeId && paramKey && (
<ContextGenerateModal
isShow={isContextGenerateModalOpen}
onClose={handleCloseContextGenerateModal}
toolNodeId={toolNodeId}
paramKey={paramKey}
codeNodeId={assembleExtractorNodeId || `${toolNodeId}_ext_${paramKey}`}
/>
)}
</div>
)
}

View File

@@ -769,11 +769,23 @@ const translation = {
'assignedVarsDescription': 'Assigned variables must be writable variables, such as conversation variables.',
},
tool: {
assembleVariables: 'Assemble Variables',
authorize: 'Authorize',
inputVars: 'Input Variables',
settings: 'Settings',
insertPlaceholder1: 'Type or press',
insertPlaceholder2: 'insert variable',
contextGenerate: {
title: 'Assemble Variables',
codeBlock: 'Code Block',
code: 'Code',
output: 'Output',
run: 'Run',
apply: 'Apply',
generating: 'Generating...',
inputPlaceholder: 'Ask for change...',
defaultAssistantMessage: 'I\'ve finished, please have a check on it.',
},
outputVars: {
text: 'tool generated content',
files: {

View File

@@ -769,11 +769,23 @@ const translation = {
'assignedVarsDescription': '赋值变量必须是可写入的变量,例如会话变量。',
},
tool: {
assembleVariables: '组合变量',
authorize: '授权',
inputVars: '输入变量',
settings: '设置',
insertPlaceholder1: '键入',
insertPlaceholder2: '插入变量',
contextGenerate: {
title: '组合变量',
codeBlock: '代码块',
code: '代码',
output: '输出',
run: '运行',
apply: '应用',
generating: '生成中...',
inputPlaceholder: '输入修改需求...',
defaultAssistantMessage: '已完成,请检查。',
},
outputVars: {
text: '工具生成的内容',
files: {

View File

@@ -747,6 +747,7 @@ const translation = {
'varNotSet': '未設置變數',
},
tool: {
assembleVariables: '組合變數',
authorize: '授權',
inputVars: '輸入變數',
outputVars: {
@@ -763,6 +764,17 @@ const translation = {
insertPlaceholder2: '插入變數',
insertPlaceholder1: '輸入或按壓',
settings: '設定',
contextGenerate: {
title: '組合變數',
codeBlock: '程式碼區塊',
code: '程式碼',
output: '輸出',
run: '執行',
apply: '套用',
generating: '生成中...',
inputPlaceholder: '輸入修改需求...',
defaultAssistantMessage: '已完成,請檢查。',
},
},
questionClassifiers: {
model: '模型',

View File

@@ -25,6 +25,38 @@ export type CodeGenRes = {
error?: string
}
export type ContextGenerateMessage = {
role: 'user' | 'assistant' | 'system'
content: string
}
export type ContextGenerateRequest = {
workflow_id: string
node_id: string
parameter_name: string
language?: 'python3' | 'javascript'
prompt_messages: ContextGenerateMessage[]
model_config: {
provider: string
name: string
completion_params?: Record<string, any>
}
}
export type ContextGenerateVariable = {
variable: string
value_selector: string[]
}
export type ContextGenerateResponse = {
variables: ContextGenerateVariable[]
code_language: string
code: string
outputs: Record<string, { type: string }>
message: string
error: string
}
export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace }: {
onData: IOnData
onCompleted: IOnCompleted
@@ -93,6 +125,12 @@ export const generateRule = (body: Record<string, any>) => {
})
}
export const generateContext = (body: ContextGenerateRequest) => {
return post<ContextGenerateResponse>('/context-generate', {
body,
})
}
export const fetchModelParams = (providerName: string, modelId: string) => {
return get(`workspaces/current/model-providers/${providerName}/models/parameter-rules`, {
params: {