feat: version select picker and versions storage

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

@ -13,7 +13,7 @@ import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app'
import { getNewVar, getVars } from '@/utils/var'
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 PromptEditor from '@/app/components/base/prompt-editor'
import ConfigContext from '@/context/debug-configuration'
@ -140,21 +140,21 @@ const Prompt: FC<ISimplePromptInput> = ({
}
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.
eventEmitter?.emit({
type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
payload: res.prompt,
payload: res.modified,
} as any)
const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_template = res.prompt
draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
draft.configs.prompt_template = res.modified
draft.configs.prompt_variables = (res.variables || []).map(key => ({ key, name: key, type: 'string', required: true }))
})
setModelConfig(newModelConfig)
setPrevPromptConfig(modelConfig.configs)
if (mode !== AppType.completion) {
setIntroduction(res.opening_statement)
setIntroduction(res.opening_statement || '')
const newFeatures = produce(features, (draft) => {
draft.opening = {
...draft.opening,

@ -20,14 +20,14 @@ import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Textarea from '@/app/components/base/textarea'
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 { AppType } from '@/types/app'
import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm'
// type
import type { AutomaticRes } from '@/service/debug'
import type { GenRes } from '@/service/debug'
import { Generator } from '@/app/components/base/icons/src/vender/other'
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 Link from 'next/link'
import Result from './result'
import useGenData from './use-gen-data'
const i18nPrefix = 'appDebug.generate'
export type IGetAutomaticResProps = {
mode: AppType
isShow: boolean
onClose: () => void
onFinished: (res: AutomaticRes) => void
onFinished: (res: GenRes) => void
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
generatorType: GeneratorType
flowId: string
flowId?: string
nodeId?: string
currentPrompt?: string
isBasicMode?: boolean
@ -163,7 +164,18 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return true
}
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(() => {
if (defaultModel) {
@ -227,21 +239,42 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
return
setLoadingTrue()
try {
const { error, ...res } = await generateRule({
flow_id: flowId,
node_id: nodeId,
current: currentPrompt,
instruction,
idea_output: ideaOutput,
model_config: model,
})
setRes(res)
if (error) {
Toast.notify({
type: 'error',
message: error,
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({
flow_id: flowId,
node_id: nodeId,
current: currentPrompt,
instruction,
idea_output: ideaOutput,
model_config: model,
})
apiRes = res
if (error) {
Toast.notify({
type: 'error',
message: error,
})
}
}
addVersion(apiRes)
}
finally {
setLoadingFalse()
@ -254,7 +287,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
}] = useBoolean(false)
const isShowAutoPromptResPlaceholder = () => {
return !isLoading && !res
return !isLoading && !current
}
return (
@ -325,7 +358,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
<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]')} />
</div>
{ !isFoldIdeaOutput && (
{!isFoldIdeaOutput && (
<Textarea
className="h-[80px]"
placeholder={t(`${i18nPrefix}.ideaOutputPlaceholder`)}
@ -354,7 +387,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
{
<div className='h-full w-0 grow p-6 pb-0'>
<Result
storageKey={`${flowId}${isBasicMode ? '' : `-${nodeId}`}`}
storageKey={storageKey}
onApply={showConfirmOverwrite}
generatorType={generatorType}
/>
@ -369,7 +402,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
isShow
onConfirm={() => {
hideShowConfirmOverwrite()
onFinished(res!)
onFinished(current!)
}}
onCancel={hideShowConfirmOverwrite}
/>

@ -1,14 +1,16 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { GeneratorType } from './types'
import PromptToast from './prompt-toast'
import Button from '@/app/components/base/button'
import useGenData from './use-gen-data'
import VersionSelector from './version-selector'
type Props = {
storageKey: string
onApply: (result: string) => void
onApply: () => void
generatorType: GeneratorType
}
@ -19,18 +21,23 @@ const Result: FC<Props> = ({
}) => {
const { t } = useTranslation()
const isGeneratorPrompt = generatorType === GeneratorType.prompt
const handleApply = useCallback(() => {
onApply('xxx')
}, [onApply])
const { current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenData({
storageKey,
})
// todo current version and version list
const current = 'xxxx'
return (
<div>
<div className='mb-3 flex items-center justify-between'>
<div className='shrink-0 text-base font-semibold leading-[160%] text-text-secondary'>{t('appDebug.generate.resTitle')}</div>
<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'>
<Button variant='primary' onClick={handleApply}>
<Button variant='primary' onClick={onApply}>
{t('appDebug.generate.apply')}
</Button>
</div>
@ -40,7 +47,7 @@ const Result: FC<Props> = ({
<PromptToast className='mt-4' />
)
}
<div className='mt-3'>{current}</div>
<div className='mt-3'>{current?.modified}</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 GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
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 { GeneratorType } from '@/app/components/app/configuration/config/automatic/types'
import { useHooksStore } from '../../../hooks-store'
@ -31,8 +31,8 @@ const PromptGeneratorBtn: FC<Props> = ({
currentPrompt,
}) => {
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = useCallback((res: AutomaticRes) => {
onGenerated?.(res.prompt)
const handleAutomaticRes = useCallback((res: GenRes) => {
onGenerated?.(res.modified)
showAutomaticFalse()
}, [onGenerated, showAutomaticFalse])
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 { ModelModeType } from '@/types/app'
import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations'
export type AutomaticRes = {
export type BasicAppFirstRes = {
prompt: string
variables: string[]
opening_statement: 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 = {
code: 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>) => {
return post<AutomaticRes>('/instruction-generate', {
return post<GenRes>('/instruction-generate', {
body,
})
}

Loading…
Cancel
Save