feat: Add Enter key handler support to assemble variables generate modal

This commit is contained in:
zhsama
2026-02-05 01:36:02 +08:00
parent 47fffedd2e
commit 0618b2532f
5 changed files with 92 additions and 24 deletions

View File

@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import type { ContextGenerateChatMessage } from '../hooks/use-context-generate'
import type { VersionOption } from '../types'
import type { WorkflowVariableBlockType } from '@/app/components/base/prompt-editor/types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { TriggerProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger'
import type { Model } from '@/types/app'
@ -10,6 +11,7 @@ import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import LoadingAnim from '@/app/components/base/chat/chat/loading-anim'
import { CodeAssistant } from '@/app/components/base/icons/src/vender/line/general'
import PromptEditor from '@/app/components/base/prompt-editor'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { cn } from '@/utils/classnames'
@ -27,6 +29,7 @@ type ChatViewProps = {
onModelChange: (newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => void
onCompletionParamsChange: (newParams: FormValue) => void
renderModelTrigger: (params: TriggerProps) => ReactNode
workflowVariableBlock: WorkflowVariableBlockType
}
const ChatView = ({
@ -43,6 +46,7 @@ const ChatView = ({
onModelChange,
onCompletionParamsChange,
renderModelTrigger,
workflowVariableBlock,
}: ChatViewProps) => {
const { t } = useTranslation()
const chatListRef = useRef<HTMLDivElement>(null)
@ -130,19 +134,16 @@ const ChatView = ({
<div className="bg-gradient-to-b from-[rgba(255,255,255,0.01)] to-background-body px-1 pb-1 pt-3">
<div className="flex min-h-[112px] flex-col justify-between overflow-hidden rounded-xl border-[0.5px] border-components-input-border-active bg-components-panel-bg shadow-shadow-shadow-5 backdrop-blur-[5px]">
<div className="flex min-h-[64px] px-3 pb-1 pt-2.5">
<textarea
value={inputValue}
onChange={e => onInputChange(e.target.value)}
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
onGenerate()
}
}}
<PromptEditor
wrapperClassName="w-full"
className="text-sm leading-5 text-text-primary"
placeholder={t('nodes.tool.contextGenerate.inputPlaceholder', { ns: 'workflow' }) as string}
className="w-full resize-none bg-transparent text-sm leading-5 text-text-primary placeholder:text-text-quaternary focus:outline-none"
disabled={isGenerating}
rows={2}
placeholderClassName="text-text-quaternary"
editable={!isGenerating}
value={inputValue}
workflowVariableBlock={workflowVariableBlock}
onChange={onInputChange}
onEnter={() => onGenerate()}
/>
</div>
<div className="flex items-end gap-2 p-2">

View File

@ -2,15 +2,18 @@ import type { ContextGenerateChatMessage } from '../hooks/use-context-generate'
import type { VersionOption } from '../types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { TriggerProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/trigger'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import type { Model } from '@/types/app'
import { RiArrowDownSLine, RiRefreshLine, RiSendPlaneLine, RiSparklingLine } from '@remixicon/react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
import PromptEditor from '@/app/components/base/prompt-editor'
import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { BlockEnum } from '@/app/components/workflow/types'
import { renderI18nObject } from '@/i18n-config'
import { cn } from '@/utils/classnames'
import ChatView from './chat-view'
@ -32,6 +35,8 @@ type LeftPanelProps = {
currentVersionIndex: number
onSelectVersion: (index: number) => void
defaultAssistantMessage: string
availableVars: NodeOutPutVar[]
availableNodes: Node[]
}
const LeftPanel = ({
@ -51,12 +56,39 @@ const LeftPanel = ({
currentVersionIndex,
onSelectVersion,
defaultAssistantMessage,
availableVars,
availableNodes,
}: LeftPanelProps) => {
const { t, i18n } = useTranslation()
const language = useMemo(() => (i18n.language || 'en-US').replace('-', '_'), [i18n.language])
const shouldShowSuggestedSkeleton = isInitView && !hasFetchedSuggestions
const suggestedSkeletonItems = useMemo(() => ([0, 1, 2]), [])
const workflowNodesMap = useMemo(() => {
return availableNodes.reduce<Record<string, Pick<Node['data'], 'title' | 'type' | 'height' | 'width' | 'position'>>>((acc, node) => {
acc[node.id] = {
title: node.data.title,
type: node.data.type,
height: node.data.height,
width: node.data.width,
position: node.data.position,
}
if (node.data.type === BlockEnum.Start) {
acc.sys = {
title: t('blocks.start', { ns: 'workflow' }),
type: BlockEnum.Start,
}
}
return acc
}, {})
}, [availableNodes, t])
const workflowVariableBlock = useMemo(() => ({
show: true,
variables: availableVars,
workflowNodesMap,
}), [availableVars, workflowNodesMap])
const renderModelTrigger = useCallback((params: TriggerProps) => {
const label = params.currentModel?.label
? renderI18nObject(params.currentModel.label, language)
@ -125,19 +157,16 @@ const LeftPanel = ({
<div className="bg-gradient-to-b from-[rgba(255,255,255,0.01)] to-background-body px-2 pb-2 pt-3">
<div className="flex h-[120px] flex-col justify-between overflow-hidden rounded-xl border-[0.5px] border-components-input-border-active bg-components-panel-bg shadow-shadow-shadow-5 backdrop-blur-[5px]">
<div className="flex min-h-[64px] px-3 pb-1 pt-2.5">
<textarea
value={inputValue}
onChange={e => onInputChange(e.target.value)}
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
onGenerate()
}
}}
<PromptEditor
wrapperClassName="w-full"
className="text-sm leading-5 text-text-primary"
placeholder={t('nodes.tool.contextGenerate.initPlaceholder', { ns: 'workflow' })}
className="w-full resize-none bg-transparent text-sm leading-5 text-text-primary placeholder:text-text-quaternary focus:outline-none"
disabled={isGenerating}
rows={2}
placeholderClassName="text-text-quaternary"
editable={!isGenerating}
value={inputValue}
workflowVariableBlock={workflowVariableBlock}
onChange={onInputChange}
onEnter={() => onGenerate()}
/>
</div>
<div className="flex items-end gap-2 p-2">
@ -208,6 +237,7 @@ const LeftPanel = ({
onModelChange={onModelChange}
onCompletionParamsChange={onCompletionParamsChange}
renderModelTrigger={renderModelTrigger}
workflowVariableBlock={workflowVariableBlock}
/>
)}
</div>

View File

@ -160,6 +160,8 @@ type UseContextGenerateResult = {
versionOptions: VersionOption[]
currentVersionLabel: string
isInitView: boolean
availableVars: NodeOutPutVar[]
availableNodes: Node[]
}
const useContextGenerate = ({
@ -539,6 +541,8 @@ const useContextGenerate = ({
versionOptions,
currentVersionLabel,
isInitView,
availableVars: resolvedAvailableVars,
availableNodes: resolvedAvailableNodes,
}
}

View File

@ -116,6 +116,8 @@ const ContextGenerateModal = forwardRef<ContextGenerateModalHandle, Props>(({
versionOptions,
currentVersionLabel,
isInitView,
availableVars: resolvedAvailableVars,
availableNodes: resolvedAvailableNodes,
} = useContextGenerate({
storageKey,
toolNodeId,
@ -241,6 +243,8 @@ const ContextGenerateModal = forwardRef<ContextGenerateModalHandle, Props>(({
currentVersionIndex={currentVersionIndex}
onSelectVersion={setCurrentVersionIndex}
defaultAssistantMessage={defaultAssistantMessage}
availableVars={resolvedAvailableVars}
availableNodes={resolvedAvailableNodes}
/>
<RightPanel
isInitView={isInitView}