mirror of
https://github.com/block/goose.git
synced 2026-07-10 00:20:17 +00:00
View model interactions (#9806)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com> Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com> Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
parent
09c8d2be5a
commit
08fb6d45f3
18 changed files with 676 additions and 116 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { getSessionDisplayName } from '../sessions';
|
||||
import { getSessionDisplayName, shouldShowNewChatTitle } from '../sessions';
|
||||
import { prependUnique } from '../hooks/useNavigationSessions';
|
||||
import type { Session } from '../api';
|
||||
import type { SessionListItem } from '../acp/sessions';
|
||||
|
|
@ -30,19 +30,51 @@ function makeListItem(overrides: Partial<SessionListItem> = {}): SessionListItem
|
|||
};
|
||||
}
|
||||
|
||||
describe('getSessionDisplayName', () => {
|
||||
it('returns the session name', () => {
|
||||
const session = makeSession({
|
||||
name: 'My Chat',
|
||||
});
|
||||
expect(getSessionDisplayName(session)).toBe('My Chat');
|
||||
describe('shouldShowNewChatTitle', () => {
|
||||
it('returns true for an empty session without a user-set name', () => {
|
||||
const session = makeSession({ message_count: 0, user_set_name: false });
|
||||
expect(shouldShowNewChatTitle(session)).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to the default title when the session name is empty', () => {
|
||||
it('returns false when the session has messages', () => {
|
||||
const session = makeSession({ message_count: 3, user_set_name: false });
|
||||
expect(shouldShowNewChatTitle(session)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when the user has set a custom name', () => {
|
||||
const session = makeSession({ message_count: 0, user_set_name: true });
|
||||
expect(shouldShowNewChatTitle(session)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when the session has a recipe', () => {
|
||||
const session = makeSession({
|
||||
name: '',
|
||||
message_count: 0,
|
||||
user_set_name: false,
|
||||
recipe: { title: 'Recipe', steps: [] } as unknown as Session['recipe'],
|
||||
});
|
||||
expect(getSessionDisplayName(session)).toBe('New Chat');
|
||||
expect(shouldShowNewChatTitle(session)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSessionDisplayName (fix for #8865)', () => {
|
||||
it('returns the user-set name for a recipe session that has been renamed', () => {
|
||||
const session = makeSession({
|
||||
name: 'My Renamed Chat',
|
||||
user_set_name: true,
|
||||
message_count: 2,
|
||||
recipe: { title: 'Some Recipe' } as unknown as Session['recipe'],
|
||||
});
|
||||
expect(getSessionDisplayName(session)).toBe('My Renamed Chat');
|
||||
});
|
||||
|
||||
it('falls back to the recipe title when the user has not renamed', () => {
|
||||
const session = makeSession({
|
||||
name: 'auto-generated',
|
||||
user_set_name: false,
|
||||
message_count: 2,
|
||||
recipe: { title: 'Some Recipe' } as unknown as Session['recipe'],
|
||||
});
|
||||
expect(getSessionDisplayName(session)).toBe('Some Recipe');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ export default function BaseChat({
|
|||
messages,
|
||||
chatState,
|
||||
setChatState,
|
||||
updateSession,
|
||||
handleSubmit,
|
||||
submitElicitationResponse,
|
||||
stopStreaming,
|
||||
|
|
@ -434,7 +435,7 @@ export default function BaseChat({
|
|||
<EnvironmentBadge className="translate-y-px" />
|
||||
</div>
|
||||
|
||||
<SessionActionsHeader session={session} />
|
||||
<SessionActionsHeader session={session} onSessionChange={updateSession} />
|
||||
|
||||
<ScrollArea
|
||||
ref={scrollRef}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import { IpcRendererEvent } from 'electron';
|
||||
import { Outlet, useLocation } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Menu } from 'lucide-react';
|
||||
import { Menu, PanelLeft } from 'lucide-react';
|
||||
import { defineMessages, useIntl } from '../../i18n';
|
||||
import { Button } from '../ui/button';
|
||||
import ChatSessionsContainer from '../ChatSessionsContainer';
|
||||
|
|
@ -18,6 +18,10 @@ const i18n = defineMessages({
|
|||
id: 'appLayout.openNavigation',
|
||||
defaultMessage: 'Open navigation',
|
||||
},
|
||||
collapseNavigation: {
|
||||
id: 'appLayout.collapseNavigation',
|
||||
defaultMessage: 'Collapse navigation',
|
||||
},
|
||||
});
|
||||
|
||||
interface AppLayoutContentProps {
|
||||
|
|
@ -60,28 +64,28 @@ const AppLayoutContent: React.FC<AppLayoutContentProps> = ({ activeSessions }) =
|
|||
|
||||
const needsTrafficLightInset = safeIsMacOS && !isFullScreen;
|
||||
const headerPadding = needsTrafficLightInset ? 'pl-[96px]' : 'pl-4';
|
||||
const headerTop = needsTrafficLightInset ? 'top-[15px]' : 'top-[11px]';
|
||||
const headerTop = needsTrafficLightInset ? 'top-[14px]' : 'top-[11px]';
|
||||
const navToggleTitle = intl.formatMessage(
|
||||
isNavExpanded ? i18n.collapseNavigation : i18n.openNavigation
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 w-full h-full relative animate-fade-in bg-background-primary flex-row">
|
||||
{/* Floating menu toggle — only when sidebar is collapsed. When expanded,
|
||||
the sidebar's own header has the collapse button. */}
|
||||
{!isNavExpanded && (
|
||||
<div
|
||||
style={{ zIndex: Z_INDEX.HEADER }}
|
||||
className={cn('absolute flex items-center gap-1', headerPadding, headerTop, 'ml-1.5')}
|
||||
<div
|
||||
style={{ zIndex: Z_INDEX.HEADER }}
|
||||
className={cn('absolute flex items-center gap-1', headerPadding, headerTop, 'ml-1.5')}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setIsNavExpanded(!isNavExpanded)}
|
||||
className="no-drag hover:!bg-background-tertiary"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
title={navToggleTitle}
|
||||
aria-label={navToggleTitle}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setIsNavExpanded(true)}
|
||||
className="no-drag hover:!bg-background-tertiary"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
title={intl.formatMessage(i18n.openNavigation)}
|
||||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isNavExpanded ? <PanelLeft className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Main content with navigation. Shared white canvas; the sidebar is a
|
||||
rounded outlined card floating on it with breathing room. */}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ChevronDown, ChevronRight, PanelLeft } from 'lucide-react';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useNavigationContext } from './NavigationContext';
|
||||
import { useConfig } from '../ConfigContext';
|
||||
|
|
@ -12,7 +12,6 @@ import {
|
|||
type NavItem,
|
||||
} from '../../hooks/useNavigationItems';
|
||||
import { AppEvents } from '../../constants/events';
|
||||
import { Goose } from '../icons/Goose';
|
||||
import { InlineEditText } from '../common/InlineEditText';
|
||||
import { SessionIndicators } from '../SessionIndicators';
|
||||
import { acpRenameSession, type SessionListItem } from '../../acp/sessions';
|
||||
|
|
@ -39,10 +38,6 @@ const i18n = defineMessages({
|
|||
id: 'navigationPanel.untitledSession',
|
||||
defaultMessage: 'Untitled session',
|
||||
},
|
||||
collapseSidebar: {
|
||||
id: 'navigationPanel.collapseSidebar',
|
||||
defaultMessage: 'Collapse sidebar',
|
||||
},
|
||||
});
|
||||
|
||||
const navItemClass = (active: boolean) =>
|
||||
|
|
@ -124,7 +119,7 @@ const SessionRow: React.FC<SessionRowProps> = ({ session, active, status, onClic
|
|||
|
||||
export const Navigation: React.FC<{ className?: string }> = ({ className }) => {
|
||||
const intl = useIntl();
|
||||
const { isNavExpanded, setIsNavExpanded } = useNavigationContext();
|
||||
const { isNavExpanded } = useNavigationContext();
|
||||
const location = useLocation();
|
||||
const { extensionsList } = useConfig();
|
||||
|
||||
|
|
@ -198,20 +193,10 @@ export const Navigation: React.FC<{ className?: string }> = ({ className }) => {
|
|||
transition={{ duration: 0.15 }}
|
||||
className={cn('bg-background-primary outline-none flex flex-col h-full', className)}
|
||||
>
|
||||
{/* Header: logo + collapse button. Top padding clears the macOS traffic lights. */}
|
||||
<div className="flex items-center justify-between px-4 pt-[34px] pb-2 no-drag">
|
||||
<Goose className="w-6 h-6 text-text-primary" />
|
||||
<button
|
||||
onClick={() => setIsNavExpanded(false)}
|
||||
className="p-1.5 rounded-md hover:bg-background-tertiary transition-colors"
|
||||
title={intl.formatMessage(i18n.collapseSidebar)}
|
||||
>
|
||||
<PanelLeft className="w-4 h-4 text-text-secondary" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-[48px] no-drag" />
|
||||
|
||||
{/* Nav items */}
|
||||
<div className="px-2 pt-2 flex flex-col gap-0.5">
|
||||
<div className="px-2 flex flex-col gap-0.5">
|
||||
{visibleItems.map((item) => (
|
||||
<NavRow
|
||||
key={item.id}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,75 @@
|
|||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, FileJson, LoaderCircle } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState, type KeyboardEvent } from 'react';
|
||||
import { ChevronDown, ChevronRight, Copy, Edit2, FileJson, LoaderCircle } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { AppEvents } from '../constants/events';
|
||||
import { defineMessages, useIntl } from '../i18n';
|
||||
import { acpExportSession } from '../acp/sessions';
|
||||
import { acpExportSession, acpForkSession, acpRenameSession } from '../acp/sessions';
|
||||
import type { Session } from '../api';
|
||||
import { getSessionDisplayName } from '../sessions';
|
||||
import { errorMessage } from '../utils/conversionUtils';
|
||||
import { cn } from '../utils';
|
||||
import { Button } from './ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from './ui/dropdown-menu';
|
||||
|
||||
const i18n = defineMessages({
|
||||
actionsLabel: {
|
||||
id: 'sessionActionsHeader.actionsLabel',
|
||||
defaultMessage: 'Session actions',
|
||||
},
|
||||
renameSession: {
|
||||
id: 'sessionActionsHeader.renameSession',
|
||||
defaultMessage: 'Rename session',
|
||||
},
|
||||
duplicateSession: {
|
||||
id: 'sessionActionsHeader.duplicateSession',
|
||||
defaultMessage: 'Duplicate session',
|
||||
},
|
||||
viewJson: {
|
||||
id: 'sessionActionsHeader.viewJson',
|
||||
defaultMessage: 'View session JSON',
|
||||
},
|
||||
renameTitle: {
|
||||
id: 'sessionActionsHeader.renameTitle',
|
||||
defaultMessage: 'Rename Session',
|
||||
},
|
||||
renamePlaceholder: {
|
||||
id: 'sessionActionsHeader.renamePlaceholder',
|
||||
defaultMessage: 'Enter session name',
|
||||
},
|
||||
cancel: {
|
||||
id: 'sessionActionsHeader.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
save: {
|
||||
id: 'sessionActionsHeader.save',
|
||||
defaultMessage: 'Save',
|
||||
},
|
||||
saving: {
|
||||
id: 'sessionActionsHeader.saving',
|
||||
defaultMessage: 'Saving...',
|
||||
},
|
||||
renamed: {
|
||||
id: 'sessionActionsHeader.renamed',
|
||||
defaultMessage: 'Session renamed',
|
||||
},
|
||||
renameFailed: {
|
||||
id: 'sessionActionsHeader.renameFailed',
|
||||
defaultMessage: 'Failed to rename session: {error}',
|
||||
},
|
||||
duplicated: {
|
||||
id: 'sessionActionsHeader.duplicated',
|
||||
defaultMessage: 'Session duplicated',
|
||||
},
|
||||
duplicateFailed: {
|
||||
id: 'sessionActionsHeader.duplicateFailed',
|
||||
defaultMessage: 'Failed to duplicate session: {error}',
|
||||
},
|
||||
jsonTitle: {
|
||||
id: 'sessionActionsHeader.jsonTitle',
|
||||
defaultMessage: 'Session JSON',
|
||||
|
|
@ -59,6 +114,7 @@ const STRING_PREVIEW_END = 56;
|
|||
|
||||
interface SessionActionsHeaderProps {
|
||||
session?: Session;
|
||||
onSessionChange: (updater: (session: Session) => Session) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
@ -253,16 +309,82 @@ function JsonTree({
|
|||
);
|
||||
}
|
||||
|
||||
export default function SessionActionsHeader({ session, className }: SessionActionsHeaderProps) {
|
||||
export default function SessionActionsHeader({
|
||||
session,
|
||||
onSessionChange,
|
||||
className,
|
||||
}: SessionActionsHeaderProps) {
|
||||
const intl = useIntl();
|
||||
const [isRenameOpen, setIsRenameOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState('');
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const [isJsonOpen, setIsJsonOpen] = useState(false);
|
||||
const [jsonValue, setJsonValue] = useState<unknown>(null);
|
||||
const [jsonText, setJsonText] = useState('');
|
||||
const [isJsonLoading, setIsJsonLoading] = useState(false);
|
||||
const [isDuplicating, setIsDuplicating] = useState(false);
|
||||
const [fullTextSelection, setFullTextSelection] = useState<FullTextSelection | null>(null);
|
||||
|
||||
const title = useMemo(() => (session ? getSessionDisplayName(session) : ''), [session]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session && isRenameOpen) {
|
||||
setRenameValue(getSessionDisplayName(session));
|
||||
}
|
||||
}, [isRenameOpen, session]);
|
||||
|
||||
const handleRename = useCallback(async () => {
|
||||
if (!session || isRenaming) return;
|
||||
|
||||
const trimmedName = renameValue.trim();
|
||||
if (!trimmedName) return;
|
||||
|
||||
if (trimmedName === session.name) {
|
||||
setIsRenameOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRenaming(true);
|
||||
try {
|
||||
await acpRenameSession(session.id, trimmedName);
|
||||
onSessionChange((current) => ({ ...current, name: trimmedName, user_set_name: true }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(AppEvents.SESSION_RENAMED, {
|
||||
detail: { sessionId: session.id, newName: trimmedName, userInitiated: true },
|
||||
})
|
||||
);
|
||||
setIsRenameOpen(false);
|
||||
toast.success(intl.formatMessage(i18n.renamed));
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
intl.formatMessage(i18n.renameFailed, {
|
||||
error: errorMessage(error, 'Unknown error'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsRenaming(false);
|
||||
}
|
||||
}, [intl, isRenaming, onSessionChange, renameValue, session]);
|
||||
|
||||
const handleDuplicate = useCallback(async () => {
|
||||
if (!session || isDuplicating) return;
|
||||
|
||||
setIsDuplicating(true);
|
||||
try {
|
||||
await acpForkSession(session.id);
|
||||
window.dispatchEvent(new CustomEvent(AppEvents.SESSION_CREATED));
|
||||
toast.success(intl.formatMessage(i18n.duplicated));
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
intl.formatMessage(i18n.duplicateFailed, {
|
||||
error: errorMessage(error, 'Unknown error'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsDuplicating(false);
|
||||
}
|
||||
}, [intl, isDuplicating, session]);
|
||||
|
||||
const handleViewJson = useCallback(async () => {
|
||||
if (!session) return;
|
||||
|
||||
|
|
@ -306,6 +428,15 @@ export default function SessionActionsHeader({ session, className }: SessionActi
|
|||
}
|
||||
}, []);
|
||||
|
||||
const handleRenameKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
void handleRename();
|
||||
}
|
||||
},
|
||||
[handleRename]
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -318,22 +449,69 @@ export default function SessionActionsHeader({ session, className }: SessionActi
|
|||
className
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-7 max-w-full items-center gap-1 rounded-md px-2.5 text-text-primary transition-colors hover:bg-background-secondary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-active"
|
||||
aria-label={intl.formatMessage(i18n.viewJson)}
|
||||
title={intl.formatMessage(i18n.viewJson)}
|
||||
onClick={() => void handleViewJson()}
|
||||
>
|
||||
<span className="truncate text-xs font-medium">{title}</span>
|
||||
{isJsonLoading ? (
|
||||
<LoaderCircle className="size-3.5 shrink-0 animate-spin text-text-secondary" />
|
||||
) : (
|
||||
<FileJson className="size-3.5 shrink-0 text-text-secondary" />
|
||||
)}
|
||||
</button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-7 max-w-full items-center gap-1 rounded-md px-2.5 text-text-primary transition-colors hover:bg-background-secondary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-active"
|
||||
aria-label={intl.formatMessage(i18n.actionsLabel)}
|
||||
>
|
||||
<span className="truncate text-xs font-medium">{title}</span>
|
||||
<ChevronDown className="size-3.5 text-text-secondary" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="center" className="w-56">
|
||||
<DropdownMenuItem onSelect={() => setIsRenameOpen(true)}>
|
||||
<Edit2 className="size-4" />
|
||||
{intl.formatMessage(i18n.renameSession)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled={isDuplicating} onSelect={() => void handleDuplicate()}>
|
||||
{isDuplicating ? (
|
||||
<LoaderCircle className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
{intl.formatMessage(i18n.duplicateSession)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => void handleViewJson()}>
|
||||
{isJsonLoading ? (
|
||||
<LoaderCircle className="size-4 animate-spin" />
|
||||
) : (
|
||||
<FileJson className="size-4" />
|
||||
)}
|
||||
{intl.formatMessage(i18n.viewJson)}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<Dialog open={isRenameOpen} onOpenChange={setIsRenameOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{intl.formatMessage(i18n.renameTitle)}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<input
|
||||
type="text"
|
||||
value={renameValue}
|
||||
onChange={(event) => setRenameValue(event.target.value)}
|
||||
onKeyDown={handleRenameKeyDown}
|
||||
placeholder={intl.formatMessage(i18n.renamePlaceholder)}
|
||||
className="w-full rounded-lg border border-border-primary bg-background-primary p-3 text-text-primary outline-none focus:ring-2 focus:ring-border-active"
|
||||
disabled={isRenaming}
|
||||
maxLength={200}
|
||||
autoFocus
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsRenameOpen(false)} disabled={isRenaming}>
|
||||
{intl.formatMessage(i18n.cancel)}
|
||||
</Button>
|
||||
<Button onClick={() => void handleRename()} disabled={isRenaming || !renameValue.trim()}>
|
||||
{isRenaming ? intl.formatMessage(i18n.saving) : intl.formatMessage(i18n.save)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isJsonOpen} onOpenChange={handleJsonOpenChange}>
|
||||
<DialogContent className="grid max-h-[85vh] grid-rows-[auto_minmax(0,1fr)_auto] sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ export function useAcpChatSession({
|
|||
|
||||
useEffect(() => {
|
||||
const handleSessionRenamed = (event: Event) => {
|
||||
const { sessionId: renamedSessionId, newName } = (
|
||||
event as CustomEvent<{ sessionId: string; newName: string }>
|
||||
const { sessionId: renamedSessionId, newName, userInitiated } = (
|
||||
event as CustomEvent<{ sessionId: string; newName: string; userInitiated?: boolean }>
|
||||
).detail;
|
||||
|
||||
if (renamedSessionId !== sessionId) {
|
||||
|
|
@ -201,11 +201,15 @@ export function useAcpChatSession({
|
|||
}
|
||||
|
||||
const currentSession = stateRef.current.session;
|
||||
if (!currentSession || currentSession.name === newName) {
|
||||
if (!currentSession || (currentSession.name === newName && !userInitiated)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedSession = { ...currentSession, name: newName };
|
||||
const updatedSession = {
|
||||
...currentSession,
|
||||
name: newName,
|
||||
...(userInitiated && { user_set_name: true }),
|
||||
};
|
||||
acpChatSessionStore.setSessionMetadata(sessionId, updatedSession);
|
||||
dispatch({ type: 'SET_SESSION', payload: updatedSession });
|
||||
};
|
||||
|
|
@ -567,6 +571,19 @@ export function useAcpChatSession({
|
|||
[sessionId]
|
||||
);
|
||||
|
||||
const updateSession = useCallback(
|
||||
(updater: (session: Session) => Session) => {
|
||||
const cached = acpChatSessionStore.getSnapshot(sessionId);
|
||||
const currentSession = stateRef.current.session ?? cached?.session;
|
||||
if (!currentSession) return;
|
||||
|
||||
const nextSession = updater(currentSession);
|
||||
acpChatSessionStore.setSessionMetadata(sessionId, nextSession);
|
||||
dispatch({ type: 'SET_SESSION', payload: nextSession });
|
||||
},
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
const cached = acpChatSessionStore.getSnapshot(sessionId);
|
||||
const maybe_cached_messages = state.session ? state.messages : cached?.messages || [];
|
||||
const maybe_cached_session = state.session ?? cached?.session;
|
||||
|
|
@ -588,6 +605,7 @@ export function useAcpChatSession({
|
|||
session: maybe_cached_session,
|
||||
chatState: state.chatState,
|
||||
setChatState,
|
||||
updateSession,
|
||||
handleSubmit,
|
||||
submitElicitationResponse,
|
||||
stopStreaming,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export interface UseChatSessionResult {
|
|||
messages: Message[];
|
||||
chatState: ChatState;
|
||||
setChatState: (state: ChatState) => void;
|
||||
updateSession: (updater: (session: Session) => Session) => void;
|
||||
handleSubmit: (input: UserInput) => Promise<void>;
|
||||
submitElicitationResponse: (
|
||||
elicitationId: string,
|
||||
|
|
|
|||
|
|
@ -476,10 +476,16 @@ export function useChatStream({
|
|||
throwOnError: true,
|
||||
});
|
||||
if (response.data?.name) {
|
||||
const nextMessageCount =
|
||||
response.data.message_count ?? currentState.session?.message_count;
|
||||
dispatch({
|
||||
type: 'SET_SESSION',
|
||||
payload: currentState.session
|
||||
? { ...currentState.session, name: response.data.name }
|
||||
? {
|
||||
...currentState.session,
|
||||
name: response.data.name,
|
||||
...(nextMessageCount !== undefined && { message_count: nextMessageCount }),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
window.dispatchEvent(
|
||||
|
|
@ -873,21 +879,32 @@ export function useChatStream({
|
|||
});
|
||||
const currentState = stateRef.current;
|
||||
const currentName = currentState.session?.name;
|
||||
const currentMessageCount = currentState.session?.message_count;
|
||||
const newName = response.data?.name;
|
||||
const newMessageCount = response.data?.message_count;
|
||||
const hasNameChange = Boolean(newName && newName !== currentName);
|
||||
const hasMessageCountChange =
|
||||
newMessageCount !== undefined && newMessageCount !== currentMessageCount;
|
||||
|
||||
if (newName && newName !== currentName) {
|
||||
if (newName && (hasNameChange || hasMessageCountChange)) {
|
||||
dispatch({
|
||||
type: 'SET_SESSION',
|
||||
payload: currentState.session
|
||||
? { ...currentState.session, name: newName }
|
||||
? {
|
||||
...currentState.session,
|
||||
name: newName,
|
||||
...(newMessageCount !== undefined && { message_count: newMessageCount }),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(AppEvents.SESSION_RENAMED, {
|
||||
detail: { sessionId, newName },
|
||||
})
|
||||
);
|
||||
return;
|
||||
if (hasNameChange) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(AppEvents.SESSION_RENAMED, {
|
||||
detail: { sessionId, newName },
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silently continue polling
|
||||
|
|
@ -1116,6 +1133,24 @@ export function useChatStream({
|
|||
dispatch({ type: 'SET_CHAT_STATE', payload: newState });
|
||||
}, []);
|
||||
|
||||
const updateSession = useCallback(
|
||||
(updater: (session: Session) => Session) => {
|
||||
const cached = resultsCache.get(sessionId);
|
||||
const currentSession = stateRef.current.session ?? cached?.session;
|
||||
if (!currentSession) return;
|
||||
|
||||
const nextSession = updater(currentSession);
|
||||
dispatch({ type: 'SET_SESSION', payload: nextSession });
|
||||
resultsCache.set(sessionId, {
|
||||
session: nextSession,
|
||||
messages: stateRef.current.messages.length
|
||||
? stateRef.current.messages
|
||||
: cached?.messages || [],
|
||||
});
|
||||
},
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
const cached = resultsCache.get(sessionId);
|
||||
const maybe_cached_messages = state.session ? state.messages : cached?.messages || [];
|
||||
const maybe_cached_session = state.session ?? cached?.session;
|
||||
|
|
@ -1137,6 +1172,7 @@ export function useChatStream({
|
|||
session: maybe_cached_session,
|
||||
chatState: state.chatState,
|
||||
setChatState,
|
||||
updateSession,
|
||||
handleSubmit,
|
||||
submitElicitationResponse,
|
||||
stopStreaming,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "Got it!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Collapse navigation"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "Open navigation"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "Chats"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "Collapse sidebar"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "No recent chats"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "Command injection detection works best when connected to WARP (required to reach the classification service)."
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Session actions"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "Failed to duplicate session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicate session"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Session duplicated"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "Failed to rename session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Enter session name"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Rename session"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Rename Session"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Session renamed"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Saving..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "¡Entendido!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Contraer navegación"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "Abrir navegación"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "Chats"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "Contraer barra lateral"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "No hay chats recientes"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "La detección de inyección de comandos funciona mejor cuando estás conectado a WARP (necesario para llegar al servicio de clasificación)."
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Acciones de la sesión"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancelar"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Cerrar"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copiar texto"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "No se pudo duplicar la sesión: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicar sesión"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Sesión duplicada"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Valor del texto"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Cargando JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "No se pudo renombrar la sesión: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Introduce el nombre de la sesión"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Renombrar sesión"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Renombrar sesión"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Sesión renombrada"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Guardar"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Guardando..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "Ver el JSON de la sesión"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "समझ गया!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Collapse navigation"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "नेविगेशन खोलें"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "चैट"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "साइडबार को संक्षिप्त करें"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "कोई हालिया चैट नहीं"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "WARP से कनेक्ट होने पर कमांड इंजेक्शन डिटेक्शन सबसे अच्छा काम करता है (वर्गीकरण सेवा तक पहुंचने के लिए आवश्यक)।"
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Session actions"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "Failed to duplicate session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicate session"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Session duplicated"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "Failed to rename session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Enter session name"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Rename session"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Rename Session"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Session renamed"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Saving..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "了解しました"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "ナビを折りたたむ"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "ナビゲーションを開く"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "チャット"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "サイドバーを折りたたむ"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "最近のチャットはありません"
|
||||
},
|
||||
|
|
@ -3758,35 +3758,71 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "コマンドインジェクション検出は、WARPに接続していると最も効果的です(分類サービスに到達するために必要です)。"
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "セッションの操作"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "キャンセル"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
"defaultMessage": "閉じる"
|
||||
},
|
||||
"sessionActionsHeader.copiedJson": {
|
||||
"defaultMessage": "Session JSON copied"
|
||||
"defaultMessage": "セッションJSONをコピーしました"
|
||||
},
|
||||
"sessionActionsHeader.copiedText": {
|
||||
"defaultMessage": "Text copied"
|
||||
"defaultMessage": "テキストをコピーしました"
|
||||
},
|
||||
"sessionActionsHeader.copyJson": {
|
||||
"defaultMessage": "Copy JSON"
|
||||
"defaultMessage": "JSONをコピー"
|
||||
},
|
||||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
"defaultMessage": "テキストをコピー"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "セッションを複製できませんでした: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "セッションを複製"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "セッションを複製しました"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
"defaultMessage": "テキスト値"
|
||||
},
|
||||
"sessionActionsHeader.jsonFailed": {
|
||||
"defaultMessage": "Failed to load session JSON: {error}"
|
||||
"defaultMessage": "セッションJSONを読み込めませんでした: {error}"
|
||||
},
|
||||
"sessionActionsHeader.jsonTitle": {
|
||||
"defaultMessage": "Session JSON"
|
||||
"defaultMessage": "セッションJSON"
|
||||
},
|
||||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
"defaultMessage": "JSONを読み込み中..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "セッションの名前を変更できませんでした: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "セッション名を入力"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "セッションの名前を変更"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "セッションの名前を変更"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "セッションの名前を変更しました"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "保存"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "保存中..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
"defaultMessage": "セッションJSONを表示"
|
||||
},
|
||||
"sessionHistory.cancel": {
|
||||
"defaultMessage": "キャンセル"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "알았어요!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "탐색 접기"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "탐색 열기"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "채팅"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "사이드바 접기"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "최근 채팅 없음"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "명령 인젝션 감지는 WARP에 연결했을 때 가장 잘 작동합니다. 분류 서비스에 연결하려면 WARP가 필요합니다."
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "세션 작업"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "취소"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "닫기"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "텍스트 복사"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "세션 복제 실패: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "세션 복제"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "세션이 복제되었습니다"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "텍스트 값"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "JSON 불러오는 중..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "세션 이름 변경 실패: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "세션 이름 입력"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "세션 이름 변경"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "세션 이름 변경"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "세션 이름이 변경되었습니다"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "저장"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "저장 중..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "세션 JSON 보기"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "Понятно!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Collapse navigation"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "Открыть навигацию"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "Чаты"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "Свернуть боковую панель"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "Нет недавних чатов"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "Обнаружение внедрения команд работает лучше всего при подключении к WARP (требуется для доступа к сервису классификации)."
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Session actions"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "Failed to duplicate session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicate session"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Session duplicated"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "Failed to rename session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Enter session name"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Rename session"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Rename Session"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Session renamed"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Saving..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "Anladım!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Collapse navigation"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "Gezinmeyi aç"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "Sohbetler"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "Kenar çubuğunu daralt"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "Son sohbet yok"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "Komut enjeksiyonu algılama, WARP bağlantısı olduğunda en iyi şekilde çalışır (sınıflandırma hizmetine erişmek için gereklidir)."
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Session actions"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "Failed to duplicate session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicate session"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Session duplicated"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "Failed to rename session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Enter session name"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Rename session"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Rename Session"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Session renamed"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Saving..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
"announcementModal.gotIt": {
|
||||
"defaultMessage": "知道了!"
|
||||
},
|
||||
"appLayout.collapseNavigation": {
|
||||
"defaultMessage": "Collapse navigation"
|
||||
},
|
||||
"appLayout.openNavigation": {
|
||||
"defaultMessage": "展开导航"
|
||||
},
|
||||
|
|
@ -2465,9 +2468,6 @@
|
|||
"navigationPanel.chats": {
|
||||
"defaultMessage": "聊天"
|
||||
},
|
||||
"navigationPanel.collapseSidebar": {
|
||||
"defaultMessage": "折叠侧边栏"
|
||||
},
|
||||
"navigationPanel.noChats": {
|
||||
"defaultMessage": "没有最近的聊天"
|
||||
},
|
||||
|
|
@ -3758,6 +3758,12 @@
|
|||
"securityToggle.warpNotice": {
|
||||
"defaultMessage": "连接到 WARP 时,命令注入检测效果最佳(访问分类服务需要 WARP)。"
|
||||
},
|
||||
"sessionActionsHeader.actionsLabel": {
|
||||
"defaultMessage": "Session actions"
|
||||
},
|
||||
"sessionActionsHeader.cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"sessionActionsHeader.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
|
|
@ -3773,6 +3779,15 @@
|
|||
"sessionActionsHeader.copyText": {
|
||||
"defaultMessage": "Copy text"
|
||||
},
|
||||
"sessionActionsHeader.duplicateFailed": {
|
||||
"defaultMessage": "Failed to duplicate session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.duplicateSession": {
|
||||
"defaultMessage": "Duplicate session"
|
||||
},
|
||||
"sessionActionsHeader.duplicated": {
|
||||
"defaultMessage": "Session duplicated"
|
||||
},
|
||||
"sessionActionsHeader.fullTextTitle": {
|
||||
"defaultMessage": "Text value"
|
||||
},
|
||||
|
|
@ -3785,6 +3800,27 @@
|
|||
"sessionActionsHeader.loadingJson": {
|
||||
"defaultMessage": "Loading JSON..."
|
||||
},
|
||||
"sessionActionsHeader.renameFailed": {
|
||||
"defaultMessage": "Failed to rename session: {error}"
|
||||
},
|
||||
"sessionActionsHeader.renamePlaceholder": {
|
||||
"defaultMessage": "Enter session name"
|
||||
},
|
||||
"sessionActionsHeader.renameSession": {
|
||||
"defaultMessage": "Rename session"
|
||||
},
|
||||
"sessionActionsHeader.renameTitle": {
|
||||
"defaultMessage": "Rename Session"
|
||||
},
|
||||
"sessionActionsHeader.renamed": {
|
||||
"defaultMessage": "Session renamed"
|
||||
},
|
||||
"sessionActionsHeader.save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"sessionActionsHeader.saving": {
|
||||
"defaultMessage": "Saving..."
|
||||
},
|
||||
"sessionActionsHeader.viewJson": {
|
||||
"defaultMessage": "View session JSON"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,20 @@ import { AppEvents } from './constants/events';
|
|||
import { decodeRecipe, Recipe } from './recipe';
|
||||
|
||||
export function getSessionDisplayName(session: Session): string {
|
||||
return session.name || DEFAULT_CHAT_TITLE;
|
||||
if (session.user_set_name) {
|
||||
return session.name;
|
||||
}
|
||||
if (session.recipe?.title) {
|
||||
return session.recipe.title;
|
||||
}
|
||||
if (shouldShowNewChatTitle(session)) {
|
||||
return DEFAULT_CHAT_TITLE;
|
||||
}
|
||||
return session.name;
|
||||
}
|
||||
|
||||
export function shouldShowNewChatTitle(session: Session): boolean {
|
||||
return !session.user_set_name && session.message_count === 0 && !session.recipe?.title;
|
||||
}
|
||||
|
||||
export function resumeSession(session: Session, setView: setViewType) {
|
||||
|
|
|
|||
6
ui/desktop/src/vite-env.d.ts
vendored
6
ui/desktop/src/vite-env.d.ts
vendored
|
|
@ -61,7 +61,11 @@ declare global {
|
|||
responseStyleChanged: CustomEvent;
|
||||
'session-created': CustomEvent<{ session?: import('./api').Session }>;
|
||||
'session-deleted': CustomEvent<{ sessionId: string }>;
|
||||
'session-renamed': CustomEvent<{ sessionId: string; newName: string }>;
|
||||
'session-renamed': CustomEvent<{
|
||||
sessionId: string;
|
||||
newName: string;
|
||||
userInitiated?: boolean;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue