feat: version select picker and versions storage

feat/enchance-prompt-and-code-fe
Joel 10 months ago
parent 677716346a
commit a85908386e

@ -13,7 +13,7 @@ import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var' import { getNewVar, getVars } from '@/utils/var'
import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn' import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
import type { AutomaticRes } from '@/service/debug' import type { GenRes } from '@/service/debug'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import PromptEditor from '@/app/components/base/prompt-editor' import PromptEditor from '@/app/components/base/prompt-editor'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
@ -140,21 +140,21 @@ const Prompt: FC<ISimplePromptInput> = ({
} }
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = (res: AutomaticRes) => { const handleAutomaticRes = (res: GenRes) => {
// put eventEmitter in first place to prevent overwrite the configs.prompt_variables.But another problem is that prompt won't hight the prompt_variables. // put eventEmitter in first place to prevent overwrite the configs.prompt_variables.But another problem is that prompt won't hight the prompt_variables.
eventEmitter?.emit({ eventEmitter?.emit({
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER, type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
payload: res.prompt, payload: res.modified,
} as any) } as any)
const newModelConfig = produce(modelConfig, (draft) => { const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_template = res.prompt draft.configs.prompt_template = res.modified
draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true })) draft.configs.prompt_variables = (res.variables || []).map(key => ({ key, name: key, type: 'string', required: true }))
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
setPrevPromptConfig(modelConfig.configs) setPrevPromptConfig(modelConfig.configs)
if (mode !== AppType.completion) { if (mode !== AppType.completion) {
setIntroduction(res.opening_statement) setIntroduction(res.opening_statement || '')
const newFeatures = produce(features, (draft) => { const newFeatures = produce(features, (draft) => {
draft.opening = { draft.opening = {
...draft.opening, ...draft.opening,

@ -20,14 +20,14 @@ import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Textarea from '@/app/components/base/textarea' import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { generateRule } from '@/service/debug' import { generateBasicAppFistTimeRule, generateRule } from '@/service/debug'
import type { CompletionParams, Model } from '@/types/app' import type { CompletionParams, Model } from '@/types/app'
import type { AppType } from '@/types/app' import type { AppType } from '@/types/app'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
// type // type
import type { AutomaticRes } from '@/service/debug' import type { GenRes } from '@/service/debug'
import { Generator } from '@/app/components/base/icons/src/vender/other' import { Generator } from '@/app/components/base/icons/src/vender/other'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
@ -41,17 +41,18 @@ import type { GeneratorType } from './types'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
import Link from 'next/link' import Link from 'next/link'
import Result from './result' import Result from './result'
import useGenData from './use-gen-data'
const i18nPrefix = 'appDebug.generate' const i18nPrefix = 'appDebug.generate'
export type IGetAutomaticResProps = { export type IGetAutomaticResProps = {
mode: AppType mode: AppType
isShow: boolean isShow: boolean
onClose: () => void onClose: () => void
onFinished: (res: AutomaticRes) => void onFinished: (res: GenRes) => void
nodesOutputVars?: NodeOutPutVar[] nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[] availableNodes?: Node[]
generatorType: GeneratorType generatorType: GeneratorType
flowId: string flowId?: string
nodeId?: string nodeId?: string
currentPrompt?: string currentPrompt?: string
isBasicMode?: boolean isBasicMode?: boolean
@ -163,7 +164,18 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return true return true
} }
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false) const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
const [res, setRes] = useState<AutomaticRes | null>(null) const storageKey = `${flowId}${isBasicMode ? '' : `-${nodeId}`}`
const { versions, addVersion, current } = useGenData({
storageKey,
})
useEffect(() => {
// if (!versions.length) {
addVersion({
modified: 'ddd',
})
// }
}, [])
useEffect(() => { useEffect(() => {
if (defaultModel) { if (defaultModel) {
@ -227,6 +239,25 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return return
setLoadingTrue() setLoadingTrue()
try { try {
let apiRes: GenRes
if (isBasicMode && !currentPrompt) {
const { error, ...res } = await generateBasicAppFistTimeRule({
instruction,
model_config: model,
no_varieable: false,
})
apiRes = {
...res,
modified: res.prompt,
} as GenRes
if (error) {
Toast.notify({
type: 'error',
message: error,
})
}
}
else {
const { error, ...res } = await generateRule({ const { error, ...res } = await generateRule({
flow_id: flowId, flow_id: flowId,
node_id: nodeId, node_id: nodeId,
@ -235,7 +266,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
idea_output: ideaOutput, idea_output: ideaOutput,
model_config: model, model_config: model,
}) })
setRes(res) apiRes = res
if (error) { if (error) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
@ -243,6 +274,8 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
}) })
} }
} }
addVersion(apiRes)
}
finally { finally {
setLoadingFalse() setLoadingFalse()
} }
@ -254,7 +287,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
}] = useBoolean(false) }] = useBoolean(false)
const isShowAutoPromptResPlaceholder = () => { const isShowAutoPromptResPlaceholder = () => {
return !isLoading && !res return !isLoading && !current
} }
return ( return (
@ -325,7 +358,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
<div className='system-xs-regular text-text-tertiary'>({t(`${i18nPrefix}.optional`)})</div> <div className='system-xs-regular text-text-tertiary'>({t(`${i18nPrefix}.optional`)})</div>
<ArrowDownRoundFill className={cn('size text-text-quaternary', isFoldIdeaOutput && 'relative top-[1px] rotate-[-90deg]')} /> <ArrowDownRoundFill className={cn('size text-text-quaternary', isFoldIdeaOutput && 'relative top-[1px] rotate-[-90deg]')} />
</div> </div>
{ !isFoldIdeaOutput && ( {!isFoldIdeaOutput && (
<Textarea <Textarea
className="h-[80px]" className="h-[80px]"
placeholder={t(`${i18nPrefix}.ideaOutputPlaceholder`)} placeholder={t(`${i18nPrefix}.ideaOutputPlaceholder`)}
@ -354,7 +387,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
{ {
<div className='h-full w-0 grow p-6 pb-0'> <div className='h-full w-0 grow p-6 pb-0'>
<Result <Result
storageKey={`${flowId}${isBasicMode ? '' : `-${nodeId}`}`} storageKey={storageKey}
onApply={showConfirmOverwrite} onApply={showConfirmOverwrite}
generatorType={generatorType} generatorType={generatorType}
/> />
@ -369,7 +402,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
isShow isShow
onConfirm={() => { onConfirm={() => {
hideShowConfirmOverwrite() hideShowConfirmOverwrite()
onFinished(res!) onFinished(current!)
}} }}
onCancel={hideShowConfirmOverwrite} onCancel={hideShowConfirmOverwrite}
/> />

@ -1,14 +1,16 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback } from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { GeneratorType } from './types' import { GeneratorType } from './types'
import PromptToast from './prompt-toast' import PromptToast from './prompt-toast'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import useGenData from './use-gen-data'
import VersionSelector from './version-selector'
type Props = { type Props = {
storageKey: string storageKey: string
onApply: (result: string) => void onApply: () => void
generatorType: GeneratorType generatorType: GeneratorType
} }
@ -19,18 +21,23 @@ const Result: FC<Props> = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const isGeneratorPrompt = generatorType === GeneratorType.prompt const isGeneratorPrompt = generatorType === GeneratorType.prompt
const handleApply = useCallback(() => { const { current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
onApply('xxx') storageKey,
}, [onApply]) })
// todo current version and version list
const current = 'xxxx'
return ( return (
<div> <div>
<div className='mb-3 flex items-center justify-between'> <div className='mb-3 flex items-center justify-between'>
<div>
<div className='shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div> <div className='shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
<VersionSelector
versionLen={versions.length}
value={currentVersionIndex}
onChange={setCurrentVersionIndex}
/>
</div>
<div className='flex space-x-2'> <div className='flex space-x-2'>
<Button variant='primary' onClick={handleApply}> <Button variant='primary' onClick={onApply}>
{t('appDebug.generate.apply')} {t('appDebug.generate.apply')}
</Button> </Button>
</div> </div>
@ -40,7 +47,7 @@ const Result: FC<Props> = ({
<PromptToast className='mt-4' /> <PromptToast className='mt-4' />
) )
} }
<div className='mt-3'>{current}</div> <div className='mt-3'>{current?.modified}</div>
</div> </div>
) )
} }

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

@ -0,0 +1,91 @@
import React from 'react'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import { useBoolean } from 'ahooks'
import cn from '@/utils/classnames'
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
type Option = {
label: string
value: number
}
type VersionSelectorProps = {
versionLen: number;
value: number;
onChange: (index: number) => void;
}
const VersionSelector: React.FC<VersionSelectorProps> = ({ versionLen, value, onChange }) => {
const [isOpen, {
setFalse: handleOpenFalse,
toggle: handleOpenToggle,
set: handleOpenSet,
}] = useBoolean(false)
const versions = Array.from({ length: versionLen }, (_, index) => ({
label: `Version ${index + 1}${index === versionLen - 1 ? ' · Latest' : ''}`,
value: index,
}))
const isLatest = value === versionLen - 1
return (
<PortalToFollowElem
placement={'bottom-start'}
offset={{
mainAxis: 4,
crossAxis: -8,
}}
open={isOpen}
onOpenChange={handleOpenSet}
>
<PortalToFollowElemTrigger
onClick={handleOpenToggle}
asChild
>
<div className='system-xs-medium flex cursor-pointer items-center text-text-secondary'>
<div>Version {value + 1}{isLatest && ' · Latest'}</div>
<RiArrowDownSLine className='size-3 ' />
</div>
</PortalToFollowElemTrigger >
<PortalToFollowElemContent className={cn(
'z-[99]',
)}>
<div
className={cn(
'w-[208px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
)}
>
<div className='system-xs-medium-uppercase flex h-[22px] items-center px-3 pl-3 text-text-tertiary'>
Versions
</div>
{
versions.map(option => (
<div
key={option.value}
className={cn(
'system-sm-medium flex h-7 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover',
)}
title={option.label}
onClick={() => {
onChange(option.value)
handleOpenFalse()
}}
>
<div className='mr-1 grow truncate px-1 pl-1'>
{option.label}
</div>
{
value === option.value && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
}
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem >
)
}
export default VersionSelector

@ -7,7 +7,7 @@ import { Generator } from '@/app/components/base/icons/src/vender/other'
import { ActionButton } from '@/app/components/base/action-button' import { ActionButton } from '@/app/components/base/action-button'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import type { AutomaticRes } from '@/service/debug' import type { GenRes } from '@/service/debug'
import type { ModelConfig, Node, NodeOutPutVar } from '@/app/components/workflow/types' import type { ModelConfig, Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' import { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
import { useHooksStore } from '../../../hooks-store' import { useHooksStore } from '../../../hooks-store'
@ -31,8 +31,8 @@ const PromptGeneratorBtn: FC<Props> = ({
currentPrompt, currentPrompt,
}) => { }) => {
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = useCallback((res: AutomaticRes) => { const handleAutomaticRes = useCallback((res: GenRes) => {
onGenerated?.(res.prompt) onGenerated?.(res.modified)
showAutomaticFalse() showAutomaticFalse()
}, [onGenerated, showAutomaticFalse]) }, [onGenerated, showAutomaticFalse])
const configsMap = useHooksStore(s => s.configsMap) const configsMap = useHooksStore(s => s.configsMap)

@ -3,12 +3,21 @@ import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessag
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug' import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { ModelModeType } from '@/types/app' import type { ModelModeType } from '@/types/app'
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
export type AutomaticRes = { export type BasicAppFirstRes = {
prompt: string prompt: string
variables: string[] variables: string[]
opening_statement: string opening_statement: string
error?: string error?: string
} }
export type GenRes = {
modified: string
message?: string // tip for human
variables?: string[] // only for basic app first time rule
opening_statement?: string // only for basic app first time rule
error?: string
}
export type CodeGenRes = { export type CodeGenRes = {
code: string code: string
language: string[] language: string[]
@ -71,8 +80,14 @@ export const fetchConversationMessages = (appId: string, conversation_id: string
}) })
} }
export const generateBasicAppFistTimeRule = (body: Record<string, any>) => {
return post<BasicAppFirstRes>('/rule-generate', {
body,
})
}
export const generateRule = (body: Record<string, any>) => { export const generateRule = (body: Record<string, any>) => {
return post<AutomaticRes>('/instruction-generate', { return post<GenRes>('/instruction-generate', {
body, body,
}) })
} }

Loading…
Cancel
Save