mirror of https://github.com/langgenius/dify.git
memory in web app
This commit is contained in:
parent
0e545d7be5
commit
61e4bc6b17
|
|
@ -7,6 +7,7 @@ import type {
|
|||
ChatConfig,
|
||||
ChatItemInTree,
|
||||
Feedback,
|
||||
Memory,
|
||||
} from '../types'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
|
|
@ -62,6 +63,12 @@ export type ChatWithHistoryContextValue = {
|
|||
}
|
||||
showChatMemory?: boolean
|
||||
setShowChatMemory: (state: boolean) => void
|
||||
memoryList: Memory[]
|
||||
clearAllMemory: () => void
|
||||
updateMemory: (memory: Memory, content: string) => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
|
||||
|
|
@ -99,5 +106,11 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
|
|||
initUserVariables: {},
|
||||
showChatMemory: false,
|
||||
setShowChatMemory: noop,
|
||||
memoryList: [],
|
||||
clearAllMemory: noop,
|
||||
updateMemory: noop,
|
||||
resetDefault: noop,
|
||||
clearAllUpdateVersion: noop,
|
||||
switchMemoryVersion: noop,
|
||||
})
|
||||
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
|||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import {
|
||||
delConversation,
|
||||
deleteMemory,
|
||||
editMemory,
|
||||
fetchChatList,
|
||||
fetchConversations,
|
||||
fetchMemories,
|
||||
generationConversationName,
|
||||
pinConversation,
|
||||
renameConversation,
|
||||
|
|
@ -41,6 +44,9 @@ import { InputVarType } from '@/app/components/workflow/types'
|
|||
import { TransferMethod } from '@/types/app'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
|
||||
import { mockMemoryList } from '@/app/components/base/chat/chat-with-history/memory/mock'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
|
|
@ -527,6 +533,59 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||
}, [isInstalledApp, appId, t, notify])
|
||||
|
||||
const [showChatMemory, setShowChatMemory] = useState(false)
|
||||
const [memoryList, setMemoryList] = useState<Memory[]>(mockMemoryList)
|
||||
|
||||
const getMemoryList = useCallback(async (currentConversationId: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, '', '', isInstalledApp, appId)
|
||||
setMemoryList(memories)
|
||||
}, [isInstalledApp, appId])
|
||||
|
||||
const clearAllMemory = useCallback(async () => {
|
||||
await deleteMemory('', isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const resetDefault = useCallback(async (memory: Memory) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, memory.spec.template, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [currentConversationId, getMemoryList, isInstalledApp, appId])
|
||||
|
||||
const clearAllUpdateVersion = useCallback(async (memory: Memory) => {
|
||||
await deleteMemory(memory.spec.id, isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const switchMemoryVersion = useCallback(async (memory: Memory, version: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, memory.spec.id, version, isInstalledApp, appId)
|
||||
const newMemory = memories[0]
|
||||
const newList = produce(memoryList, (draft) => {
|
||||
const index = draft.findIndex(item => item.spec.id === memory.spec.id)
|
||||
if (index !== -1)
|
||||
draft[index] = newMemory
|
||||
})
|
||||
setMemoryList(newList)
|
||||
}, [memoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
const updateMemory = useCallback(async (memory: Memory, content: string) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, content, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [getMemoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
useEffect(() => {
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
return {
|
||||
isInstalledApp,
|
||||
|
|
@ -576,5 +635,11 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
|||
sidebarCollapseState,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useChatWithHistoryContext()
|
||||
const isSidebarCollapsed = sidebarCollapseState
|
||||
const customConfig = appData?.custom_config
|
||||
|
|
@ -101,14 +107,34 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
|||
)}
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<MemoryPanel showChatMemory={showChatMemory} />
|
||||
<MemoryPanel
|
||||
isMobile={isMobile}
|
||||
showChatMemory={showChatMemory}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
memoryList={memoryList}
|
||||
clearAllMemory={clearAllMemory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
)}
|
||||
{isMobile && showChatMemory && (
|
||||
<div className='fixed inset-0 z-50 flex flex-row-reverse bg-background-overlay p-1 backdrop-blur-sm'
|
||||
onClick={() => setShowChatMemory(false)}
|
||||
>
|
||||
<div className='flex h-full w-[360px] rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
|
||||
<MemoryPanel showChatMemory={showChatMemory} />
|
||||
<MemoryPanel
|
||||
isMobile={isMobile}
|
||||
showChatMemory={showChatMemory}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
memoryList={memoryList}
|
||||
clearAllMemory={clearAllMemory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -169,6 +195,12 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
|||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useChatWithHistory(installedAppInfo)
|
||||
|
||||
return (
|
||||
|
|
@ -214,6 +246,12 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
|||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}}>
|
||||
<ChatWithHistory className={className} />
|
||||
</ChatWithHistoryContext.Provider>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
|
|
@ -10,14 +10,14 @@ import Button from '@/app/components/base/button'
|
|||
import Textarea from '@/app/components/base/textarea'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { MemoryItem } from '../type'
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
import { noop } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: MemoryItem
|
||||
show: boolean
|
||||
onConfirm: (info: MemoryItem) => Promise<void>
|
||||
onConfirm: (info: MemoryItem, content: string) => Promise<void>
|
||||
onHide: () => void
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
|
@ -30,14 +30,25 @@ const MemoryEditModal = ({
|
|||
isMobile,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = React.useState(memory.content)
|
||||
const [content, setContent] = React.useState(memory.value)
|
||||
|
||||
const versionTag = useMemo(() => {
|
||||
const res = `${t('share.chat.memory.updateVersion.update')} ${memory.version}`
|
||||
if (memory.edited_by_user)
|
||||
return `${res} · ${t('share.chat.memory.updateVersion.edited')}`
|
||||
return res
|
||||
}, [memory.version, t])
|
||||
|
||||
const reset = () => {
|
||||
setContent(memory.value)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!content.trim()) {
|
||||
Toast.notify({ type: 'error', message: 'content is required' })
|
||||
return
|
||||
}
|
||||
onConfirm({ ...memory, content })
|
||||
onConfirm(memory, content)
|
||||
onHide()
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +67,8 @@ const MemoryEditModal = ({
|
|||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} className='!h-4' />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow px-4'>
|
||||
|
|
@ -71,7 +82,7 @@ const MemoryEditModal = ({
|
|||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={onHide}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
<Button className='mr-3' onClick={reset}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,8 +104,8 @@ const MemoryEditModal = ({
|
|||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-6'>
|
||||
|
|
@ -108,7 +119,7 @@ const MemoryEditModal = ({
|
|||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={onHide}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
<Button className='mr-3' onClick={reset}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
|
|
@ -10,53 +10,100 @@ import Badge from '@/app/components/base/badge'
|
|||
import Indicator from '@/app/components/header/indicator'
|
||||
import Operation from './operation'
|
||||
import MemoryEditModal from './edit-modal'
|
||||
import type { MemoryItem } from '../type'
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: MemoryItem
|
||||
isMobile?: boolean
|
||||
memory: MemoryItem
|
||||
updateMemory: (memory: MemoryItem, content: string) => void
|
||||
resetDefault: (memory: MemoryItem) => void
|
||||
clearAllUpdateVersion: (memory: MemoryItem) => void
|
||||
switchMemoryVersion: (memory: MemoryItem, version: string) => void
|
||||
}
|
||||
|
||||
const MemoryCard: React.FC<Props> = ({ memory, isMobile }) => {
|
||||
const MemoryCard: React.FC<Props> = ({
|
||||
isMobile,
|
||||
memory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isHovering, setIsHovering] = React.useState(false)
|
||||
const [showEditModal, setShowEditModal] = React.useState(false)
|
||||
|
||||
const versionTag = useMemo(() => {
|
||||
const res = `${t('share.chat.memory.updateVersion.update')} ${memory.version}`
|
||||
if (memory.edited_by_user)
|
||||
return `${res} · ${t('share.chat.memory.updateVersion.edited')}`
|
||||
return res
|
||||
}, [memory.version, t])
|
||||
|
||||
const isLatest = useMemo(() => {
|
||||
if (memory.conversation_metadata)
|
||||
return memory.conversation_metadata.visible_count === memory.spec.preserved_turns
|
||||
|
||||
return true
|
||||
}, [memory])
|
||||
|
||||
const waitMergeCount = useMemo(() => {
|
||||
if (memory.conversation_metadata)
|
||||
return memory.conversation_metadata.visible_count - memory.spec.preserved_turns
|
||||
|
||||
return 0
|
||||
}, [memory])
|
||||
|
||||
const prevVersion = () => {
|
||||
if (memory.version > 1)
|
||||
switchMemoryVersion(memory, (memory.version - 1).toString())
|
||||
}
|
||||
|
||||
const nextVersion = () => {
|
||||
switchMemoryVersion(memory, (memory.version + 1).toString())
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('group mb-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-md', !memory.status && 'pb-2')}
|
||||
className={cn('group mb-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-md')}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<div className='flex items-end justify-between pb-1 pl-4 pr-2 pt-2'>
|
||||
<div className='relative flex items-end justify-between pb-1 pl-4 pr-2 pt-2'>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} className='!h-4' />}
|
||||
</div>
|
||||
{isHovering && (
|
||||
<div className='hover:bg-components-actionbar-bg-hover flex items-center gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md'>
|
||||
<ActionButton><RiArrowUpSLine className='h-4 w-4' /></ActionButton>
|
||||
<ActionButton><RiArrowDownSLine className='h-4 w-4' /></ActionButton>
|
||||
<Operation onEdit={() => {
|
||||
setShowEditModal(true)
|
||||
setIsHovering(false)
|
||||
}} />
|
||||
<div className='hover:bg-components-actionbar-bg-hover absolute bottom-0 right-2 flex items-center gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md'>
|
||||
<ActionButton onClick={prevVersion}><RiArrowUpSLine className='h-4 w-4' /></ActionButton>
|
||||
<ActionButton onClick={nextVersion}><RiArrowDownSLine className='h-4 w-4' /></ActionButton>
|
||||
<Operation
|
||||
memory={memory}
|
||||
onEdit={() => {
|
||||
setShowEditModal(true)
|
||||
setIsHovering(false)
|
||||
}}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='system-xs-regular line-clamp-[12] px-4 pb-2 pt-1 text-text-tertiary'>{memory.content}</div>
|
||||
{memory.status === 'latest' && (
|
||||
<div className='system-xs-regular line-clamp-[12] px-4 pb-2 pt-1 text-text-tertiary'>{memory.value}</div>
|
||||
{isLatest && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.latestVersion')}</div>
|
||||
<Indicator color='green' />
|
||||
</div>
|
||||
)}
|
||||
{memory.status === 'needUpdate' && (
|
||||
{!isLatest && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.notLatestVersion', { num: memory.mergeCount })}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.notLatestVersion', { num: waitMergeCount })}</div>
|
||||
<Indicator color='orange' />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -66,9 +113,8 @@ const MemoryCard: React.FC<Props> = ({ memory, isMobile }) => {
|
|||
isMobile={isMobile}
|
||||
show={showEditModal}
|
||||
memory={memory}
|
||||
onConfirm={async (info) => {
|
||||
// Handle confirm logic here
|
||||
console.log('Memory updated:', info)
|
||||
onConfirm={async (info, content) => {
|
||||
await updateMemory(info, content)
|
||||
setShowEditModal(false)
|
||||
}}
|
||||
onHide={() => setShowEditModal(false)}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,23 @@ import {
|
|||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: Memory
|
||||
onEdit: () => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = ({
|
||||
memory,
|
||||
onEdit,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
|
|
@ -52,8 +61,8 @@ const OperationDropdown: FC<Props> = ({
|
|||
<div className='w-[220px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
|
||||
<div className='p-1'>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={onEdit}>{t('share.chat.memory.operations.edit')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('share.chat.memory.operations.reset')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive'>{t('share.chat.memory.operations.clear')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={() => resetDefault(memory)}>{t('share.chat.memory.operations.reset')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive' onClick={() => clearAllUpdateVersion(memory)}>{t('share.chat.memory.operations.clear')}</div>
|
||||
</div>
|
||||
<Divider className='!my-0 !h-px bg-divider-subtle' />
|
||||
<div className='px-1 py-2'>
|
||||
|
|
|
|||
|
|
@ -6,29 +6,40 @@ import {
|
|||
} from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
useChatWithHistoryContext,
|
||||
} from '../context'
|
||||
import MemoryCard from './card'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
import { mockMemoryList } from './mock'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
|
||||
type Props = {
|
||||
isMobile?: boolean
|
||||
showChatMemory?: boolean
|
||||
setShowChatMemory: (show: boolean) => void
|
||||
memoryList: Memory[]
|
||||
clearAllMemory: () => void
|
||||
updateMemory: (memory: Memory, content: string) => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
const MemoryPanel: React.FC<Props> = ({ showChatMemory }) => {
|
||||
const MemoryPanel: React.FC<Props> = ({
|
||||
isMobile,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
isMobile,
|
||||
setShowChatMemory,
|
||||
} = useChatWithHistoryContext()
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-full w-[360px] shrink-0 flex-col rounded-2xl border-[0.5px] border-components-panel-border-subtle bg-chatbot-bg transition-all ease-in-out',
|
||||
showChatMemory ? 'w-[360px]' : 'w-0',
|
||||
showChatMemory ? 'w-[360px]' : 'w-0 opacity-0',
|
||||
)}>
|
||||
<div className='flex shrink-0 items-center border-b-[0.5px] border-components-panel-border-subtle pl-4 pr-3.5 pt-2'>
|
||||
<div className='system-md-semibold-uppercase grow py-3 text-text-primary'>{t('share.chat.memory.title')}</div>
|
||||
|
|
@ -37,15 +48,30 @@ const MemoryPanel: React.FC<Props> = ({ showChatMemory }) => {
|
|||
</ActionButton>
|
||||
</div>
|
||||
<div className='h-0 grow overflow-y-auto px-3 pt-2'>
|
||||
{mockMemoryList.map(memory => (
|
||||
<MemoryCard key={memory.name} memory={memory} isMobile={isMobile} />
|
||||
{memoryList.map(memory => (
|
||||
<MemoryCard
|
||||
key={memory.spec.id}
|
||||
isMobile={isMobile}
|
||||
memory={memory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
))}
|
||||
<div className='flex items-center justify-center'>
|
||||
<Button variant='ghost'>
|
||||
<RiDeleteBinLine className='mr-1 h-3.5 w-3.5' />
|
||||
{t('share.chat.memory.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
{memoryList.length > 0 && (
|
||||
<div className='flex items-center justify-center'>
|
||||
<Button variant='ghost' onClick={clearAllMemory}>
|
||||
<RiDeleteBinLine className='mr-1 h-3.5 w-3.5' />
|
||||
{t('share.chat.memory.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{memoryList.length === 0 && (
|
||||
<div className='system-xs-regular flex items-center justify-center py-2 text-text-tertiary'>
|
||||
{t('share.chat.memory.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,29 +1,96 @@
|
|||
import type { MemoryItem } from './type'
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
|
||||
export const mockMemoryList: MemoryItem[] = [
|
||||
{
|
||||
name: 'learning_companion',
|
||||
content: `Learning Goal: [What you\'re studying]
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Learning Goal: [What you\'re studying]
|
||||
Current Level: [Beginner/Intermediate/Advanced]
|
||||
Learning Style: [Visual, hands-on, theoretical, etc.]
|
||||
Progress: [Topics mastered, current focus]
|
||||
Preferred Pace: [Fast/moderate/slow explanations]
|
||||
Background: [Relevant experience or education]
|
||||
Time Constraints: [Available study time]`,
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 1,
|
||||
edited_by_user: false,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'learning_companion',
|
||||
name: 'Learning Companion',
|
||||
description: 'A companion to help with learning goals',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 5,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'research_partner',
|
||||
content: `Research Topic: [Your research topic]
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Research Topic: [Your research topic]
|
||||
Current Progress: [Literature review, experiments, etc.]
|
||||
Challenges: [What you\'re struggling with]
|
||||
Goals: [Short-term and long-term research goals]`,
|
||||
status: 'latest',
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 1,
|
||||
edited_by_user: false,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'research_partner',
|
||||
name: 'research_partner',
|
||||
description: 'A companion to help with research goals',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 3,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'code_partner',
|
||||
content: `Code Context: [Brief description of the codebase]
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Code Context: [Brief description of the codebase]
|
||||
Current Issues: [Bugs, technical debt, etc.]
|
||||
Goals: [Features to implement, improvements to make]`,
|
||||
status: 'needUpdate',
|
||||
mergeCount: 5,
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 3,
|
||||
edited_by_user: true,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'code_partner',
|
||||
name: 'code_partner',
|
||||
description: 'A companion to help with code-related tasks',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 5,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
export type MemoryItem = {
|
||||
name: string;
|
||||
content: string;
|
||||
status?: 'latest' | 'needUpdate';
|
||||
mergeCount?: number;
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import Loading from '@/app/components/base/loading'
|
|||
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
import Header from '@/app/components/base/chat/embedded-chatbot/header'
|
||||
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
|
||||
import MemoryPanel from './memory'
|
||||
import MemoryPanel from '@/app/components/base/chat/chat-with-history/memory'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import cn from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
|
|
@ -94,14 +94,18 @@ const Chatbot = () => {
|
|||
</div>
|
||||
)}
|
||||
{showChatMemory && (
|
||||
<div className='fixed inset-0 z-50 flex flex-row-reverse bg-background-overlay p-1 backdrop-blur-sm'
|
||||
onClick={() => setShowChatMemory(false)}
|
||||
>
|
||||
<div className='flex h-full w-[360px] rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
|
||||
<MemoryPanel showChatMemory={showChatMemory} />
|
||||
</div>
|
||||
<div className='fixed inset-0 z-50 flex flex-row-reverse bg-background-overlay p-1 backdrop-blur-sm'
|
||||
onClick={() => setShowChatMemory(false)}
|
||||
>
|
||||
<div className='flex h-full w-[360px] rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
|
||||
<MemoryPanel
|
||||
showChatMemory={showChatMemory}
|
||||
isMobile={isMobile}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { MemoryItem } from '../type'
|
||||
import { noop } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: MemoryItem
|
||||
show: boolean
|
||||
onConfirm: (info: MemoryItem) => Promise<void>
|
||||
onHide: () => void
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
||||
const MemoryEditModal = ({
|
||||
memory,
|
||||
show = false,
|
||||
onConfirm,
|
||||
onHide,
|
||||
isMobile,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = React.useState(memory.content)
|
||||
|
||||
const submit = () => {
|
||||
if (!content.trim()) {
|
||||
Toast.notify({ type: 'error', message: 'content is required' })
|
||||
return
|
||||
}
|
||||
onConfirm({ ...memory, content })
|
||||
onHide()
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex flex-col bg-background-overlay pt-3 backdrop-blur-sm'
|
||||
onClick={onHide}
|
||||
>
|
||||
<div className='relative flex w-full grow flex-col rounded-t-xl bg-components-panel-bg shadow-xl' onClick={e => e.stopPropagation()}>
|
||||
<div className='absolute right-4 top-4 cursor-pointer p-2'>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='h-5 w-5' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='p-4 pb-3'>
|
||||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow px-4'>
|
||||
<Textarea
|
||||
className='h-full'
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse items-center p-4'>
|
||||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={onHide}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={noop}
|
||||
className={cn('relative !max-w-[800px]', 'p-0')}
|
||||
>
|
||||
<div className='absolute right-5 top-5 cursor-pointer p-2'>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='h-5 w-5' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='p-6 pb-3'>
|
||||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-6'>
|
||||
<Textarea
|
||||
className='h-[562px]'
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse items-center p-6 pt-5'>
|
||||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={onHide}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryEditModal
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowUpSLine,
|
||||
} from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Operation from './operation'
|
||||
import MemoryEditModal from './edit-modal'
|
||||
import type { MemoryItem } from '../type'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: MemoryItem
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
||||
const MemoryCard: React.FC<Props> = ({ memory, isMobile }) => {
|
||||
const { t } = useTranslation()
|
||||
const [isHovering, setIsHovering] = React.useState(false)
|
||||
const [showEditModal, setShowEditModal] = React.useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('group mb-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-md', !memory.status && 'pb-2')}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<div className='flex items-end justify-between pb-1 pl-4 pr-2 pt-2'>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.name}</div>
|
||||
<Badge text={`${t('share.chat.memory.updateVersion.update')} 2`} />
|
||||
</div>
|
||||
{isHovering && (
|
||||
<div className='hover:bg-components-actionbar-bg-hover flex items-center gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md'>
|
||||
<ActionButton><RiArrowUpSLine className='h-4 w-4' /></ActionButton>
|
||||
<ActionButton><RiArrowDownSLine className='h-4 w-4' /></ActionButton>
|
||||
<Operation onEdit={() => {
|
||||
setShowEditModal(true)
|
||||
setIsHovering(false)
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='system-xs-regular line-clamp-[12] px-4 pb-2 pt-1 text-text-tertiary'>{memory.content}</div>
|
||||
{memory.status === 'latest' && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.latestVersion')}</div>
|
||||
<Indicator color='green' />
|
||||
</div>
|
||||
)}
|
||||
{memory.status === 'needUpdate' && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.notLatestVersion', { num: memory.mergeCount })}</div>
|
||||
<Indicator color='orange' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showEditModal && (
|
||||
<MemoryEditModal
|
||||
isMobile={isMobile}
|
||||
show={showEditModal}
|
||||
memory={memory}
|
||||
onConfirm={async (info) => {
|
||||
// Handle confirm logic here
|
||||
console.log('Memory updated:', info)
|
||||
setShowEditModal(false)
|
||||
}}
|
||||
onHide={() => setShowEditModal(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryCard
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCheckLine, RiMoreFill } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
onEdit: () => void
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = ({
|
||||
onEdit,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
const setOpen = useCallback((v: boolean) => {
|
||||
doSetOpen(v)
|
||||
openRef.current = v
|
||||
}, [doSetOpen])
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<div>
|
||||
<ActionButton className={cn(open && 'bg-state-base-hover')}>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[220px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
|
||||
<div className='p-1'>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={onEdit}>{t('share.chat.memory.operations.edit')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('share.chat.memory.operations.reset')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive'>{t('share.chat.memory.operations.clear')}</div>
|
||||
</div>
|
||||
<Divider className='!my-0 !h-px bg-divider-subtle' />
|
||||
<div className='px-1 py-2'>
|
||||
<div className='system-xs-medium-uppercase px-3 pb-0.5 pt-1 text-text-tertiary'>{t('share.chat.memory.updateVersion.title')}</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default React.memo(OperationDropdown)
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
} from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import {
|
||||
useEmbeddedChatbotContext,
|
||||
} from '../context'
|
||||
import MemoryCard from './card'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
import { mockMemoryList } from './mock'
|
||||
|
||||
type Props = {
|
||||
showChatMemory?: boolean
|
||||
}
|
||||
|
||||
const MemoryPanel: React.FC<Props> = ({ showChatMemory }) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
isMobile,
|
||||
setShowChatMemory,
|
||||
} = useEmbeddedChatbotContext()
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-full w-[360px] shrink-0 flex-col rounded-2xl border-[0.5px] border-components-panel-border-subtle bg-chatbot-bg transition-all ease-in-out',
|
||||
showChatMemory ? 'w-[360px]' : 'w-0',
|
||||
)}>
|
||||
<div className='flex shrink-0 items-center border-b-[0.5px] border-components-panel-border-subtle pl-4 pr-3.5 pt-2'>
|
||||
<div className='system-md-semibold-uppercase grow py-3 text-text-primary'>{t('share.chat.memory.title')}</div>
|
||||
<ActionButton size='l' onClick={() => setShowChatMemory(false)}>
|
||||
<RiCloseLine className='h-[18px] w-[18px]' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='h-0 grow overflow-y-auto px-3 pt-2'>
|
||||
{mockMemoryList.map(memory => (
|
||||
<MemoryCard key={memory.name} memory={memory} isMobile={isMobile} />
|
||||
))}
|
||||
<div className='flex items-center justify-center'>
|
||||
<Button variant='ghost'>
|
||||
<RiDeleteBinLine className='mr-1 h-3.5 w-3.5' />
|
||||
{t('share.chat.memory.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryPanel
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import type { MemoryItem } from './type'
|
||||
export const mockMemoryList: MemoryItem[] = [
|
||||
{
|
||||
name: 'learning_companion',
|
||||
content: `Learning Goal: [What you\'re studying]
|
||||
Current Level: [Beginner/Intermediate/Advanced]
|
||||
Learning Style: [Visual, hands-on, theoretical, etc.]
|
||||
Progress: [Topics mastered, current focus]
|
||||
Preferred Pace: [Fast/moderate/slow explanations]
|
||||
Background: [Relevant experience or education]
|
||||
Time Constraints: [Available study time]`,
|
||||
},
|
||||
{
|
||||
name: 'research_partner',
|
||||
content: `Research Topic: [Your research topic]
|
||||
Current Progress: [Literature review, experiments, etc.]
|
||||
Challenges: [What you\'re struggling with]
|
||||
Goals: [Short-term and long-term research goals]`,
|
||||
status: 'latest',
|
||||
},
|
||||
{
|
||||
name: 'code_partner',
|
||||
content: `Code Context: [Brief description of the codebase]
|
||||
Current Issues: [Bugs, technical debt, etc.]
|
||||
Goals: [Features to implement, improvements to make]`,
|
||||
status: 'needUpdate',
|
||||
mergeCount: 5,
|
||||
},
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export type MemoryItem = {
|
||||
name: string;
|
||||
content: string;
|
||||
status?: 'latest' | 'needUpdate';
|
||||
mergeCount?: number;
|
||||
}
|
||||
|
|
@ -95,3 +95,36 @@ export type Feedback = {
|
|||
rating: 'like' | 'dislike' | null
|
||||
content?: string | null
|
||||
}
|
||||
|
||||
export type MemorySpec = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
template: string // default value
|
||||
instruction: string
|
||||
scope: string // app or node
|
||||
term: string // session or persistent
|
||||
strategy: string
|
||||
update_turns: number
|
||||
preserved_turns: number
|
||||
schedule_mode: string // sync or async
|
||||
end_user_visible: boolean
|
||||
end_user_editable: boolean
|
||||
}
|
||||
|
||||
export type ConversationMetaData = {
|
||||
type: string // mutable_visible_window
|
||||
visible_count: number // visible_count - preserved_turns = N messages waiting merged
|
||||
}
|
||||
|
||||
export type Memory = {
|
||||
tenant_id: string
|
||||
value: string
|
||||
app_id: string
|
||||
conversation_id?: string
|
||||
node_id?: string
|
||||
version: number
|
||||
edited_by_user: boolean
|
||||
conversation_metadata?: ConversationMetaData
|
||||
spec: MemorySpec
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const translation = {
|
|||
cancel: 'Cancel',
|
||||
save: 'Save & Apply',
|
||||
},
|
||||
empty: 'No memory yet.',
|
||||
},
|
||||
},
|
||||
generation: {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ const translation = {
|
|||
cancel: '取消',
|
||||
save: '保存并应用',
|
||||
},
|
||||
empty: '暂无记忆。',
|
||||
},
|
||||
},
|
||||
generation: {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import type {
|
|||
IOnWorkflowStarted,
|
||||
} from './base'
|
||||
import {
|
||||
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost,
|
||||
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost,
|
||||
del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, put as consolePut,
|
||||
delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, putPublic as put, ssePost,
|
||||
} from './base'
|
||||
import type { FeedbackType } from '@/app/components/base/chat/chat/type'
|
||||
import type {
|
||||
|
|
@ -32,10 +32,10 @@ import type {
|
|||
AppMeta,
|
||||
ConversationItem,
|
||||
} from '@/models/share'
|
||||
import type { ChatConfig } from '@/app/components/base/chat/types'
|
||||
import type { ChatConfig, Memory } from '@/app/components/base/chat/types'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
|
||||
function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) {
|
||||
function getAction(action: 'get' | 'post' | 'del' | 'patch' | 'put', isInstalledApp: boolean) {
|
||||
switch (action) {
|
||||
case 'get':
|
||||
return isInstalledApp ? consoleGet : get
|
||||
|
|
@ -45,6 +45,8 @@ function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boo
|
|||
return isInstalledApp ? consolePatch : patch
|
||||
case 'del':
|
||||
return isInstalledApp ? consoleDel : del
|
||||
case 'put':
|
||||
return isInstalledApp ? consolePut : put
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,3 +310,30 @@ export const getUserCanAccess = (appId: string, isInstalledApp: boolean) => {
|
|||
export const getAppAccessModeByAppCode = (appCode: string) => {
|
||||
return get<{ accessMode: AccessMode }>(`/webapp/access-mode?appCode=${appCode}`)
|
||||
}
|
||||
|
||||
export const fetchMemories = async (
|
||||
conversation_id = '',
|
||||
memory_id = '',
|
||||
version = '',
|
||||
isInstalledApp: boolean,
|
||||
installedAppId = '',
|
||||
) => {
|
||||
return (getAction('get', isInstalledApp))(getUrl('/memories', isInstalledApp, installedAppId), { params: { conversation_id, memory_id, version } }) as Promise<Memory[]>
|
||||
}
|
||||
|
||||
export const deleteMemory = (
|
||||
memoryId = '',
|
||||
isInstalledApp: boolean,
|
||||
installedAppId = '',
|
||||
) => {
|
||||
return (getAction('del', isInstalledApp))(getUrl('/memories', isInstalledApp, installedAppId), { params: { id: memoryId } })
|
||||
}
|
||||
|
||||
export const editMemory = (
|
||||
memoryId: string,
|
||||
value: string,
|
||||
isInstalledApp: boolean,
|
||||
installedAppId = '',
|
||||
) => {
|
||||
return (getAction('put', isInstalledApp))(getUrl('memory-edit', isInstalledApp, installedAppId), { body: { id: memoryId, update: value } })
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue