mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-06 05:55:28 +08:00
fix(agents): use correct SWRInfinite cache key and add optimistic update for session editing
Fixes SWRInfinite cache key usage and adds optimistic updates for session editing.
This commit is contained in:
@@ -55,6 +55,8 @@ const processError = (error: unknown, fallbackMessage: string) => {
|
||||
return new Error(fallbackMessage, { cause: error })
|
||||
}
|
||||
|
||||
export const DEFAULT_SESSION_PAGE_SIZE = 20
|
||||
|
||||
export class AgentApiClient {
|
||||
private axios: Axios
|
||||
private apiVersion: ApiVersion = 'v1'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DEFAULT_SESSION_PAGE_SIZE } from '@renderer/api/agent'
|
||||
import type {
|
||||
AgentSessionEntity,
|
||||
CreateAgentSessionResponse,
|
||||
@@ -12,9 +13,7 @@ import useSWRInfinite from 'swr/infinite'
|
||||
|
||||
import { useAgentClient } from './useAgentClient'
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 20
|
||||
|
||||
export const useSessions = (agentId: string | null, pageSize = DEFAULT_PAGE_SIZE) => {
|
||||
export const useSessions = (agentId: string | null, pageSize = DEFAULT_SESSION_PAGE_SIZE) => {
|
||||
const { t } = useTranslation()
|
||||
const client = useAgentClient()
|
||||
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
import { DEFAULT_SESSION_PAGE_SIZE } from '@renderer/api/agent'
|
||||
import type { AgentSessionEntity, ListAgentSessionsResponse, UpdateSessionForm } from '@renderer/types'
|
||||
import type { UpdateAgentBaseOptions, UpdateAgentSessionFunction } from '@renderer/types/agent'
|
||||
import { getErrorMessage } from '@renderer/utils/error'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { mutate } from 'swr'
|
||||
import { unstable_serialize } from 'swr/infinite'
|
||||
|
||||
import { useAgentClient } from './useAgentClient'
|
||||
|
||||
type InfiniteData = ListAgentSessionsResponse[]
|
||||
|
||||
const mutateInfiniteList = (
|
||||
infKey: string,
|
||||
sessionId: string,
|
||||
updater: (session: AgentSessionEntity) => AgentSessionEntity
|
||||
) => {
|
||||
mutate<InfiniteData>(
|
||||
infKey,
|
||||
(prev) => {
|
||||
if (!prev) return prev
|
||||
return prev.map((page) => ({
|
||||
...page,
|
||||
data: page.data.map((session) => (session.id === sessionId ? updater(session) : session))
|
||||
}))
|
||||
},
|
||||
{ revalidate: false }
|
||||
)
|
||||
}
|
||||
|
||||
export const useUpdateSession = (agentId: string | null) => {
|
||||
const { t } = useTranslation()
|
||||
const client = useAgentClient()
|
||||
@@ -17,20 +39,26 @@ export const useUpdateSession = (agentId: string | null) => {
|
||||
const paths = client.getSessionPaths(agentId)
|
||||
const listKey = paths.base
|
||||
const sessionId = form.id
|
||||
const itemKey = paths.withId(sessionId)
|
||||
const infKey = unstable_serialize(() => [listKey, 0, DEFAULT_SESSION_PAGE_SIZE])
|
||||
|
||||
// Optimistic update
|
||||
mutateInfiniteList(infKey, sessionId, (session) => ({ ...session, ...form }))
|
||||
mutate<AgentSessionEntity>(itemKey, (prev) => (prev ? { ...prev, ...form } : prev), { revalidate: false })
|
||||
|
||||
try {
|
||||
const itemKey = paths.withId(sessionId)
|
||||
// may change to optimistic update
|
||||
const result = await client.updateSession(agentId, form)
|
||||
mutate<ListAgentSessionsResponse['data']>(
|
||||
listKey,
|
||||
(prev) => prev?.map((session) => (session.id === result.id ? result : session)) ?? []
|
||||
)
|
||||
mutate(itemKey, result)
|
||||
// Update with server response
|
||||
mutateInfiniteList(infKey, sessionId, () => result)
|
||||
mutate(itemKey, result, { revalidate: false })
|
||||
if (options?.showSuccessToast ?? true) {
|
||||
window.toast.success(t('common.update_success'))
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
// Rollback: revalidate to get fresh data
|
||||
mutate(infKey)
|
||||
mutate(itemKey)
|
||||
window.toast.error({ title: t('agent.session.update.error.failed'), description: getErrorMessage(error) })
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ const SessionItem = ({ session, agentId, onDelete, onPress }: SessionItemProps)
|
||||
<SessionEditInput {...inputProps} style={{ opacity: isSaving ? 0.5 : 1 }} />
|
||||
) : (
|
||||
<>
|
||||
<SessionName>
|
||||
<div className="truncate text-[13px]">
|
||||
<SessionLabel session={session} />
|
||||
</SessionName>
|
||||
</div>
|
||||
<DeleteButton />
|
||||
</>
|
||||
)}
|
||||
@@ -256,15 +256,6 @@ const SessionNameContainer = styled.div`
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const SessionName = styled.div`
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const SessionEditInput = styled.input`
|
||||
background: var(--color-background);
|
||||
border: none;
|
||||
|
||||
@@ -90,13 +90,13 @@ export type AgentLabelProps = {
|
||||
hideIcon?: boolean
|
||||
}
|
||||
|
||||
export const AgentLabel: React.FC<AgentLabelProps> = ({ agent, classNames, hideIcon }) => {
|
||||
export const AgentLabel = ({ agent, classNames, hideIcon }: AgentLabelProps) => {
|
||||
const emoji = agent?.configuration?.avatar
|
||||
|
||||
return (
|
||||
<div className={cn('flex w-full items-center gap-2 truncate', classNames?.container)}>
|
||||
{!hideIcon && <EmojiIcon emoji={emoji || '⭐️'} className={classNames?.avatar} size={24} />}
|
||||
<span className={cn('truncate', 'text-[var(--color-text)]', classNames?.name)}>
|
||||
<span className={cn('truncate', 'text-(--color-text)', classNames?.name)}>
|
||||
{agent?.name ?? (agent?.type ? getAgentTypeLabel(agent.type) : '')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -108,11 +108,11 @@ export type SessionLabelProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const SessionLabel: React.FC<SessionLabelProps> = ({ session, className }) => {
|
||||
export const SessionLabel = ({ session, className }: SessionLabelProps) => {
|
||||
const displayName = session?.name ?? session?.id
|
||||
return (
|
||||
<>
|
||||
<span className={cn('truncate text-[var(--color-text)] text-sm', className)}>{displayName}</span>
|
||||
<span className={cn('truncate text-(--color-text) text-sm', className)}>{displayName}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export const SettingsContainer: React.FC<React.ComponentPropsWithRef<'div'> & Sc
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Scrollbar className={cn('p-[16px]', className)} {...props}>
|
||||
<Scrollbar className={cn('p-4', className)} {...props}>
|
||||
{children}
|
||||
</Scrollbar>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user