mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 14:29:15 +08:00
Backport: PR 11558 to v2
This commit is contained in:
parent
4f0cba15a0
commit
13abc2d653
@ -2,6 +2,7 @@ import { loggerService } from '@logger'
|
|||||||
import type { QuickPanelTriggerInfo } from '@renderer/components/QuickPanel'
|
import type { QuickPanelTriggerInfo } from '@renderer/components/QuickPanel'
|
||||||
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
|
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||||
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
|
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
|
||||||
|
import { cacheService } from '@renderer/data/CacheService'
|
||||||
import { useSession } from '@renderer/hooks/agents/useSession'
|
import { useSession } from '@renderer/hooks/agents/useSession'
|
||||||
import { useInputText } from '@renderer/hooks/useInputText'
|
import { useInputText } from '@renderer/hooks/useInputText'
|
||||||
import { selectNewTopicLoading } from '@renderer/hooks/useMessageOperations'
|
import { selectNewTopicLoading } from '@renderer/hooks/useMessageOperations'
|
||||||
@ -41,19 +42,10 @@ import { getInputbarConfig } from './registry'
|
|||||||
import { TopicType } from './types'
|
import { TopicType } from './types'
|
||||||
|
|
||||||
const logger = loggerService.withContext('AgentSessionInputbar')
|
const logger = loggerService.withContext('AgentSessionInputbar')
|
||||||
const agentSessionDraftCache = new Map<string, string>()
|
|
||||||
|
|
||||||
const readDraftFromCache = (key: string): string => {
|
const DRAFT_CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
|
||||||
return agentSessionDraftCache.get(key) ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeDraftToCache = (key: string, value: string) => {
|
const getAgentDraftCacheKey = (agentId: string) => `agent.session.draft.${agentId}`
|
||||||
if (!value) {
|
|
||||||
agentSessionDraftCache.delete(key)
|
|
||||||
} else {
|
|
||||||
agentSessionDraftCache.set(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
agentId: string
|
agentId: string
|
||||||
@ -170,16 +162,15 @@ const AgentSessionInputbarInner: FC<InnerProps> = ({ assistant, agentId, session
|
|||||||
const scope = TopicType.Session
|
const scope = TopicType.Session
|
||||||
const config = getInputbarConfig(scope)
|
const config = getInputbarConfig(scope)
|
||||||
|
|
||||||
// Use shared hooks for text and textarea management
|
// Use shared hooks for text and textarea management with draft persistence
|
||||||
const initialDraft = useMemo(() => readDraftFromCache(agentId), [agentId])
|
const draftCacheKey = getAgentDraftCacheKey(agentId)
|
||||||
const persistDraft = useCallback((next: string) => writeDraftToCache(agentId, next), [agentId])
|
|
||||||
const {
|
const {
|
||||||
text,
|
text,
|
||||||
setText,
|
setText,
|
||||||
isEmpty: inputEmpty
|
isEmpty: inputEmpty
|
||||||
} = useInputText({
|
} = useInputText({
|
||||||
initialValue: initialDraft,
|
initialValue: cacheService.get<string>(draftCacheKey) ?? '',
|
||||||
onChange: persistDraft
|
onChange: (value) => cacheService.set(draftCacheKey, value, DRAFT_CACHE_TTL)
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
textareaRef,
|
textareaRef,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
isVisionModels,
|
isVisionModels,
|
||||||
isWebSearchModel
|
isWebSearchModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
|
import { cacheService } from '@renderer/data/CacheService'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useInputText } from '@renderer/hooks/useInputText'
|
import { useInputText } from '@renderer/hooks/useInputText'
|
||||||
@ -39,7 +40,7 @@ import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
|||||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { InputbarCore } from './components/InputbarCore'
|
import { InputbarCore } from './components/InputbarCore'
|
||||||
@ -51,6 +52,17 @@ import TokenCount from './TokenCount'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('Inputbar')
|
const logger = loggerService.withContext('Inputbar')
|
||||||
|
|
||||||
|
const INPUTBAR_DRAFT_CACHE_KEY = 'inputbar.draft.text'
|
||||||
|
const DRAFT_CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
|
||||||
|
|
||||||
|
const getMentionedModelsCacheKey = (assistantId: string) => `inputbar.mentioned.models.${assistantId}`
|
||||||
|
|
||||||
|
const getValidatedCachedModels = (assistantId: string): Model[] => {
|
||||||
|
const cached = cacheService.get<Model[]>(getMentionedModelsCacheKey(assistantId))
|
||||||
|
if (!Array.isArray(cached)) return []
|
||||||
|
return cached.filter((model) => model?.id && model?.name)
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
@ -80,16 +92,18 @@ const Inputbar: FC<Props> = ({ assistant: initialAssistant, setActiveTopic, topi
|
|||||||
toggleExpanded: () => {}
|
toggleExpanded: () => {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [initialMentionedModels] = useState(() => getValidatedCachedModels(initialAssistant.id))
|
||||||
|
|
||||||
const initialState = useMemo(
|
const initialState = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
files: [] as FileType[],
|
files: [] as FileType[],
|
||||||
mentionedModels: [] as Model[],
|
mentionedModels: initialMentionedModels,
|
||||||
selectedKnowledgeBases: initialAssistant.knowledge_bases ?? [],
|
selectedKnowledgeBases: initialAssistant.knowledge_bases ?? [],
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
couldAddImageFile: false,
|
couldAddImageFile: false,
|
||||||
extensions: [] as string[]
|
extensions: [] as string[]
|
||||||
}),
|
}),
|
||||||
[initialAssistant.knowledge_bases]
|
[initialMentionedModels, initialAssistant.knowledge_bases]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -121,7 +135,10 @@ const InputbarInner: FC<InputbarInnerProps> = ({ assistant: initialAssistant, se
|
|||||||
const { setFiles, setMentionedModels, setSelectedKnowledgeBases } = useInputbarToolsDispatch()
|
const { setFiles, setMentionedModels, setSelectedKnowledgeBases } = useInputbarToolsDispatch()
|
||||||
const { setCouldAddImageFile } = useInputbarToolsInternalDispatch()
|
const { setCouldAddImageFile } = useInputbarToolsInternalDispatch()
|
||||||
|
|
||||||
const { text, setText } = useInputText()
|
const { text, setText } = useInputText({
|
||||||
|
initialValue: cacheService.get<string>(INPUTBAR_DRAFT_CACHE_KEY) ?? '',
|
||||||
|
onChange: (value) => cacheService.set(INPUTBAR_DRAFT_CACHE_KEY, value, DRAFT_CACHE_TTL)
|
||||||
|
})
|
||||||
const {
|
const {
|
||||||
textareaRef,
|
textareaRef,
|
||||||
resize: resizeTextArea,
|
resize: resizeTextArea,
|
||||||
@ -192,6 +209,14 @@ const InputbarInner: FC<InputbarInnerProps> = ({ assistant: initialAssistant, se
|
|||||||
setCouldAddImageFile(canAddImageFile)
|
setCouldAddImageFile(canAddImageFile)
|
||||||
}, [canAddImageFile, setCouldAddImageFile])
|
}, [canAddImageFile, setCouldAddImageFile])
|
||||||
|
|
||||||
|
const onUnmount = useEffectEvent((id: string) => {
|
||||||
|
cacheService.set(getMentionedModelsCacheKey(id), mentionedModels, DRAFT_CACHE_TTL)
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => onUnmount(assistant.id)
|
||||||
|
}, [assistant.id, onUnmount])
|
||||||
|
|
||||||
const placeholderText = enableQuickPanelTriggers
|
const placeholderText = enableQuickPanelTriggers
|
||||||
? t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) })
|
? t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) })
|
||||||
: t('chat.input.placeholder_without_triggers', {
|
: t('chat.input.placeholder_without_triggers', {
|
||||||
|
|||||||
@ -151,11 +151,8 @@ export const InputbarCore: FC<InputbarCoreProps> = ({
|
|||||||
|
|
||||||
const setText = useCallback<React.Dispatch<React.SetStateAction<string>>>(
|
const setText = useCallback<React.Dispatch<React.SetStateAction<string>>>(
|
||||||
(value) => {
|
(value) => {
|
||||||
if (typeof value === 'function') {
|
const newText = typeof value === 'function' ? value(textRef.current) : value
|
||||||
onTextChange(value(textRef.current))
|
onTextChange(newText)
|
||||||
} else {
|
|
||||||
onTextChange(value)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[onTextChange]
|
[onTextChange]
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user