diff --git a/ui/desktop/src/__tests__/sessions.test.ts b/ui/desktop/src/__tests__/sessions.test.ts index 3bb48febcf..c5baf703de 100644 --- a/ui/desktop/src/__tests__/sessions.test.ts +++ b/ui/desktop/src/__tests__/sessions.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { shouldShowNewChatTitle } from '../sessions'; -import { getSessionDisplayName } from '../hooks/useNavigationSessions'; +import { getSessionDisplayName, sortAndTrim, prependUnique } from '../hooks/useNavigationSessions'; import type { Session } from '../api'; // Helper to build a minimal Session object for testing. @@ -43,70 +43,6 @@ describe('shouldShowNewChatTitle', () => { }); }); -describe('session reuse scoping (fix for #7601)', () => { - // Simulates the core logic extracted from handleNewChat in useNavigationSessions.ts. - // Before the fix: `sessions.find(s => shouldShowNewChatTitle(s))` picked the - // first global empty session regardless of which window called it. - // After the fix: only the current window's activeSessionId is considered. - function findReusableSession( - sessions: Session[], - activeSessionId: string | undefined - ): Session | undefined { - const currentActive = activeSessionId - ? sessions.find((s) => s.id === activeSessionId) - : undefined; - if (currentActive && shouldShowNewChatTitle(currentActive)) { - return currentActive; - } - return undefined; - } - - const emptySessionA = makeSession({ id: 'empty-a', message_count: 0, user_set_name: false }); - const emptySessionB = makeSession({ id: 'empty-b', message_count: 0, user_set_name: false }); - const usedSession = makeSession({ id: 'used-c', message_count: 5, user_set_name: true }); - - const allSessions = [emptySessionA, emptySessionB, usedSession]; - - it("window A only reuses its own active empty session, not window B's", () => { - // Window A has emptySessionA active, Window B has emptySessionB active. - // Under the old logic, both would grab emptySessionA (the first in the list). - const windowAResult = findReusableSession(allSessions, 'empty-a'); - const windowBResult = findReusableSession(allSessions, 'empty-b'); - - expect(windowAResult?.id).toBe('empty-a'); - expect(windowBResult?.id).toBe('empty-b'); - // They never collide on the same session. - expect(windowAResult?.id).not.toBe(windowBResult?.id); - }); - - it('does not reuse a session that has messages even if it is active', () => { - const result = findReusableSession(allSessions, 'used-c'); - expect(result).toBeUndefined(); - }); - - it('returns undefined when there is no active session id', () => { - const result = findReusableSession(allSessions, undefined); - expect(result).toBeUndefined(); - }); - - it('returns undefined when the active session id is not in the list', () => { - const result = findReusableSession(allSessions, 'nonexistent'); - expect(result).toBeUndefined(); - }); - - it('demonstrates the old bug: global find would give same session to both windows', () => { - // Old logic (before fix) - both windows get the same session. - const oldLogicFind = (sessions: Session[]) => sessions.find((s) => shouldShowNewChatTitle(s)); - - const windowAOld = oldLogicFind(allSessions); - const windowBOld = oldLogicFind(allSessions); - - // Both windows would grab the exact same session - the bug. - expect(windowAOld?.id).toBe(windowBOld?.id); - expect(windowAOld?.id).toBe('empty-a'); - }); -}); - describe('getSessionDisplayName (fix for #8865)', () => { it('returns the user-set name for a recipe session that has been renamed', () => { const session = makeSession({ @@ -128,3 +64,63 @@ describe('getSessionDisplayName (fix for #8865)', () => { expect(getSessionDisplayName(session)).toBe('Some Recipe'); }); }); + +describe('sortAndTrim', () => { + it('sorts by updated_at descending', () => { + const result = sortAndTrim([ + makeSession({ + id: 'old-but-active', + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-03-01T00:00:00Z', + }), + makeSession({ + id: 'newer-but-idle', + created_at: '2024-03-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', + }), + makeSession({ + id: 'mid', + created_at: '2024-02-01T00:00:00Z', + updated_at: '2024-02-01T00:00:00Z', + }), + ]); + expect(result.map((s) => s.id)).toEqual(['old-but-active', 'mid', 'newer-but-idle']); + }); + + it('caps the list at 25 sessions', () => { + const sessions = Array.from({ length: 40 }, (_, i) => + makeSession({ id: `s-${i}`, created_at: new Date(2024, 0, i + 1).toISOString() }) + ); + expect(sortAndTrim(sessions)).toHaveLength(25); + }); + + it('does not mutate the input array', () => { + const input = [ + makeSession({ id: 'a', updated_at: '2024-01-01T00:00:00Z' }), + makeSession({ id: 'b', updated_at: '2024-02-01T00:00:00Z' }), + ]; + sortAndTrim(input); + expect(input.map((s) => s.id)).toEqual(['a', 'b']); + }); +}); + +describe('prependUnique', () => { + it('prepends a new session to the front', () => { + const prev = [makeSession({ id: 'a' })]; + const result = prependUnique(prev, makeSession({ id: 'b' })); + expect(result.map((s) => s.id)).toEqual(['b', 'a']); + }); + + it('returns the same reference when the session is already present', () => { + const prev = [makeSession({ id: 'a' }), makeSession({ id: 'b' })]; + const result = prependUnique(prev, makeSession({ id: 'a' })); + expect(result).toBe(prev); + }); + + it('caps the list at 25 sessions', () => { + const prev = Array.from({ length: 25 }, (_, i) => makeSession({ id: `s-${i}` })); + const result = prependUnique(prev, makeSession({ id: 'new' })); + expect(result).toHaveLength(25); + expect(result[0].id).toBe('new'); + }); +}); diff --git a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.test.tsx b/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.test.tsx deleted file mode 100644 index 67eeafbfbe..0000000000 --- a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.test.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, type RenderOptions, screen, waitFor, fireEvent } from '@testing-library/react'; -import { BottomMenuModeSelection } from './BottomMenuModeSelection'; -import { IntlTestWrapper } from '../../i18n/test-utils'; - -const renderWithIntl = (ui: React.ReactElement, options?: RenderOptions) => - render(ui, { wrapper: IntlTestWrapper, ...options }); - -let mockConfig: Record = {}; -const mockUpdateSession = vi.fn().mockResolvedValue({}); -const mockGetSession = vi.fn().mockResolvedValue({ data: null }); - -vi.mock('../ConfigContext', () => ({ - useConfig: () => ({ - config: mockConfig, - }), -})); - -vi.mock('../../utils/analytics', () => ({ - trackModeChanged: vi.fn(), -})); - -vi.mock('../../api', () => ({ - updateSession: (...args: unknown[]) => mockUpdateSession(...args), - getSession: (...args: unknown[]) => mockGetSession(...args), -})); - -// Radix dropdown doesn't open in jsdom — render children directly -vi.mock('../ui/dropdown-menu', () => ({ - DropdownMenu: ({ children }: { children: React.ReactNode }) =>
{children}
, - DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) =>
{children}
, - DropdownMenuContent: ({ children }: { children: React.ReactNode }) =>
{children}
, - DropdownMenuItem: ({ children }: { children: React.ReactNode }) =>
{children}
, -})); - -describe('BottomMenuModeSelection', () => { - beforeEach(() => { - vi.clearAllMocks(); - mockConfig = {}; - }); - - it('displays mode from config when no session', async () => { - mockConfig.GOOSE_MODE = 'approve'; - renderWithIntl(); - await waitFor(() => { - expect(screen.getByText('manual')).toBeInTheDocument(); - }); - }); - - it('defaults to auto when config has no mode', async () => { - mockConfig.GOOSE_MODE = undefined; - renderWithIntl(); - await waitFor(() => { - expect(screen.getByText('autonomous')).toBeInTheDocument(); - }); - }); - - it('fetches mode from session when sessionId is present', async () => { - mockConfig.GOOSE_MODE = 'auto'; - mockGetSession.mockResolvedValue({ data: { goose_mode: 'approve' } }); - renderWithIntl(); - await waitFor(() => { - expect(screen.getByText('manual')).toBeInTheDocument(); - }); - expect(mockGetSession).toHaveBeenCalledWith({ - path: { session_id: 'test-session-123' }, - }); - }); - - it('calls updateSession and does not write global config', async () => { - mockConfig.GOOSE_MODE = 'auto'; - renderWithIntl(); - - fireEvent.click(screen.getByText('Manual')); - - await waitFor(() => { - expect(mockUpdateSession).toHaveBeenCalledWith({ - body: { session_id: 'test-session-123', goose_mode: 'approve' }, - }); - }); - }); - - it('does not call updateSession when sessionId is null', async () => { - mockConfig.GOOSE_MODE = 'auto'; - renderWithIntl(); - - fireEvent.click(screen.getByText('Manual')); - - await waitFor(() => { - expect(screen.getByText('manual')).toBeInTheDocument(); - }); - expect(mockUpdateSession).not.toHaveBeenCalled(); - }); - - it('ignores stale session fetch after sessionId changes', async () => { - let resolveA: (value: unknown) => void; - const promiseA = new Promise((resolve) => { - resolveA = resolve; - }); - - mockGetSession - .mockImplementationOnce(() => promiseA) - .mockResolvedValueOnce({ data: { goose_mode: 'auto' } }); - - const { rerender } = renderWithIntl(); - rerender(); - - await waitFor(() => { - expect(screen.getByText('autonomous')).toBeInTheDocument(); - }); - - resolveA!({ data: { goose_mode: 'approve' } }); - - await waitFor(() => { - expect(screen.getByText('autonomous')).toBeInTheDocument(); - }); - expect(screen.queryByText('manual')).not.toBeInTheDocument(); - }); -}); diff --git a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx b/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx deleted file mode 100644 index ae7a6f7ca2..0000000000 --- a/ui/desktop/src/components/bottom_menu/BottomMenuModeSelection.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Tornado } from 'lucide-react'; -import { all_goose_modes, ModeSelectionItem } from '../settings/mode/ModeSelectionItem'; -import { useConfig } from '../ConfigContext'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '../ui/dropdown-menu'; -import { trackModeChanged } from '../../utils/analytics'; -import { getSession, updateSession } from '../../api'; -import { defineMessages, useIntl } from '../../i18n'; - -const i18n = defineMessages({ - autoFallback: { - id: 'bottomMenuModeSelection.autoFallback', - defaultMessage: 'auto', - }, - automaticModeDescription: { - id: 'bottomMenuModeSelection.automaticModeDescription', - defaultMessage: 'Automatic mode selection', - }, - currentModeTitle: { - id: 'bottomMenuModeSelection.currentModeTitle', - defaultMessage: 'Current mode: {label} - {description}', - }, -}); - -export const BottomMenuModeSelection = ({ sessionId }: { sessionId: string | null }) => { - const intl = useIntl(); - const [gooseMode, setGooseMode] = useState('auto'); - const { config } = useConfig(); - - useEffect(() => { - let cancelled = false; - if (sessionId) { - getSession({ path: { session_id: sessionId } }).then((res) => { - if (!cancelled && res.data?.goose_mode) { - setGooseMode(res.data.goose_mode); - } - }); - } else { - const mode = config.GOOSE_MODE as string | undefined; - if (mode) { - setGooseMode(mode); - } - } - return () => { - cancelled = true; - }; - }, [sessionId, config.GOOSE_MODE]); - - const handleModeChange = async (newMode: string) => { - if (gooseMode === newMode) { - return; - } - - try { - if (sessionId) { - await updateSession({ body: { session_id: sessionId, goose_mode: newMode } }); - } - setGooseMode(newMode); - trackModeChanged(gooseMode, newMode); - } catch (error) { - console.error('Error updating goose mode:', error); - throw new Error(`Failed to store new goose mode: ${newMode}`); - } - }; - - function getValueByKey(key: string): string { - const mode = all_goose_modes.find((mode) => mode.key === key); - if (!mode) return intl.formatMessage(i18n.autoFallback); - return intl.formatMessage(mode.labelDescriptor); - } - - function getModeDescription(key: string): string { - const mode = all_goose_modes.find((mode) => mode.key === key); - if (!mode) return intl.formatMessage(i18n.automaticModeDescription); - return intl.formatMessage(mode.descriptionDescriptor); - } - - return ( -
- - - - - {getValueByKey(gooseMode).toLowerCase()} - - - - {all_goose_modes.map((mode) => ( - - - - ))} - - -
- ); -}; diff --git a/ui/desktop/src/components/sessions/SessionItem.tsx b/ui/desktop/src/components/sessions/SessionItem.tsx deleted file mode 100644 index 3c80703106..0000000000 --- a/ui/desktop/src/components/sessions/SessionItem.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { defineMessages, useIntl } from '../../i18n'; -import { Card } from '../ui/card'; -import { formatDate } from '../../utils/date'; -import { Session } from '../../api'; -import { shouldShowNewChatTitle } from '../../sessions'; -import { DEFAULT_CHAT_TITLE } from '../../contexts/ChatContext'; - -const i18n = defineMessages({ - messageCount: { - id: 'sessionItem.messageCount', - defaultMessage: '{count} messages', - }, -}); - -interface SessionItemProps { - session: Session; - extraActions?: React.ReactNode; -} - -const SessionItem: React.FC = ({ session, extraActions }) => { - const intl = useIntl(); - const displayName = shouldShowNewChatTitle(session) ? DEFAULT_CHAT_TITLE : session.name; - - return ( - -
-
{displayName}
-
- {formatDate(session.updated_at)} • {intl.formatMessage(i18n.messageCount, { count: session.message_count })} -
-
{session.working_dir}
-
- {extraActions &&
{extraActions}
} -
- ); -}; - -export default SessionItem; diff --git a/ui/desktop/src/hooks/useNavigationSessions.ts b/ui/desktop/src/hooks/useNavigationSessions.ts index 4bd86aed76..180ff5f434 100644 --- a/ui/desktop/src/hooks/useNavigationSessions.ts +++ b/ui/desktop/src/hooks/useNavigationSessions.ts @@ -2,43 +2,36 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'; import { getSession, listSessions } from '../api'; import { useChatContext } from '../contexts/ChatContext'; -import { useConfig } from '../components/ConfigContext'; -import { useNavigation } from './useNavigation'; -import { startNewSession, resumeSession, shouldShowNewChatTitle } from '../sessions'; -import { getInitialWorkingDir } from '../utils/workingDir'; +import { shouldShowNewChatTitle } from '../sessions'; import { AppEvents } from '../constants/events'; import type { Session } from '../api'; const MAX_RECENT_SESSIONS = 25; -interface UseNavigationSessionsOptions { - onNavigate?: () => void; - fetchOnMount?: boolean; +export function sortAndTrim(sessions: Session[]): Session[] { + return [...sessions] + .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) + .slice(0, MAX_RECENT_SESSIONS); } -export function useNavigationSessions(options: UseNavigationSessionsOptions = {}) { - const { onNavigate, fetchOnMount = false } = options; +export function prependUnique(prev: Session[], session: Session): Session[] { + if (prev.some((s) => s.id === session.id)) return prev; + return [session, ...prev].slice(0, MAX_RECENT_SESSIONS); +} +export function useNavigationSessions() { const navigate = useNavigate(); const location = useLocation(); const [searchParams] = useSearchParams(); const chatContext = useChatContext(); - const { extensionsList } = useConfig(); - const setView = useNavigation(); const [recentSessions, setRecentSessions] = useState([]); - const sessionsRef = useRef([]); const lastSessionIdRef = useRef(null); - const isCreatingSessionRef = useRef(false); const activeSessionId = searchParams.get('resumeSessionId') ?? undefined; const currentSessionId = location.pathname === '/pair' ? searchParams.get('resumeSessionId') : null; - useEffect(() => { - sessionsRef.current = recentSessions; - }, [recentSessions]); - useEffect(() => { if (currentSessionId) { lastSessionIdRef.current = currentSessionId; @@ -49,33 +42,21 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} try { const response = await listSessions({ throwOnError: false }); if (response.data) { - const sorted = [...response.data.sessions] - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .slice(0, MAX_RECENT_SESSIONS); - setRecentSessions(sorted); - sessionsRef.current = response.data.sessions; + const apiSessions = sortAndTrim(response.data.sessions); + setRecentSessions(apiSessions); } } catch (error) { console.error('Failed to fetch sessions:', error); } }, []); - useEffect(() => { - if (fetchOnMount) { - fetchSessions(); - } - }, [fetchOnMount, fetchSessions]); - useEffect(() => { if (!activeSessionId) return; if (recentSessions.some((s) => s.id === activeSessionId)) return; getSession({ path: { session_id: activeSessionId }, throwOnError: false }).then((response) => { if (!response.data) return; - setRecentSessions((prev) => { - if (prev.some((s) => s.id === activeSessionId)) return prev; - return [response.data as Session, ...prev].slice(0, MAX_RECENT_SESSIONS); - }); + setRecentSessions((prev) => prependUnique(prev, response.data as Session)); }); }, [activeSessionId, recentSessions]); @@ -86,11 +67,7 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} const handleSessionCreated = (event: Event) => { const { session } = (event as CustomEvent<{ session?: Session }>).detail || {}; if (session) { - setRecentSessions((prev) => { - if (prev.some((s) => s.id === session.id)) return prev; - return [session, ...prev].slice(0, MAX_RECENT_SESSIONS); - }); - sessionsRef.current = [session, ...sessionsRef.current.filter((s) => s.id !== session.id)]; + setRecentSessions((prev) => prependUnique(prev, session)); } if (isPolling) return; @@ -106,15 +83,8 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} try { const response = await listSessions({ throwOnError: false }); if (response.data) { - const apiSessions = response.data.sessions.slice(0, MAX_RECENT_SESSIONS); - setRecentSessions((prev) => { - const emptyLocalSessions = prev.filter( - (local) => - local.message_count === 0 && !apiSessions.some((api) => api.id === local.id) - ); - return [...emptyLocalSessions, ...apiSessions].slice(0, MAX_RECENT_SESSIONS); - }); - sessionsRef.current = response.data.sessions; + const apiSessions = sortAndTrim(response.data.sessions); + setRecentSessions(apiSessions); } } catch (error) { console.error('Failed to poll sessions:', error); @@ -145,7 +115,6 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} const { sessionId } = (event as CustomEvent<{ sessionId: string }>).detail; setRecentSessions((prev) => prev.filter((session) => session.id !== sessionId)); - sessionsRef.current = sessionsRef.current.filter((session) => session.id !== sessionId); if (lastSessionIdRef.current === sessionId) { lastSessionIdRef.current = null; @@ -154,17 +123,8 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} listSessions({ throwOnError: false }) .then((response) => { if (version !== fetchVersion || !response.data) return; - const apiSessions = [...response.data.sessions] - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .slice(0, MAX_RECENT_SESSIONS); - setRecentSessions((prev) => { - const emptyLocalSessions = prev.filter( - (local) => - local.message_count === 0 && !apiSessions.some((api) => api.id === local.id) - ); - return [...emptyLocalSessions, ...apiSessions].slice(0, MAX_RECENT_SESSIONS); - }); - sessionsRef.current = response.data.sessions; + const apiSessions = sortAndTrim(response.data.sessions); + setRecentSessions(apiSessions); }) .catch((error) => console.error('Failed to fetch sessions:', error)); }; @@ -176,9 +136,6 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} setRecentSessions((prev) => prev.map((session) => (session.id === sessionId ? { ...session, name: newName } : session)) ); - sessionsRef.current = sessionsRef.current.map((session) => - session.id === sessionId ? { ...session, name: newName } : session - ); }; window.addEventListener(AppEvents.SESSION_DELETED, handleSessionDeleted); @@ -203,54 +160,22 @@ export function useNavigationSessions(options: UseNavigationSessionsOptions = {} } else { navigate(path); } - onNavigate?.(); }, - [navigate, currentSessionId, chatContext?.chat?.sessionId, onNavigate] + [navigate, currentSessionId, chatContext?.chat?.sessionId] ); - const handleNewChat = useCallback(async () => { - if (isCreatingSessionRef.current) return; - - // Only reuse the current window's own active session if it is empty. - // Previously this grabbed the first empty session globally, which caused - // multiple windows to claim the same empty session after a restart/upgrade. - const currentActiveSession = activeSessionId - ? sessionsRef.current.find((s) => s.id === activeSessionId) - : undefined; - const canReuseActive = currentActiveSession && shouldShowNewChatTitle(currentActiveSession); - - if (canReuseActive) { - resumeSession(currentActiveSession, setView); - } else { - isCreatingSessionRef.current = true; - try { - await startNewSession('', setView, getInitialWorkingDir(), { - allExtensions: extensionsList, - }); - } finally { - setTimeout(() => { - isCreatingSessionRef.current = false; - }, 1000); - } - } - onNavigate?.(); - }, [setView, onNavigate, extensionsList, activeSessionId]); - const handleSessionClick = useCallback( (sessionId: string) => { navigate(`/pair?resumeSessionId=${sessionId}`); - onNavigate?.(); }, - [navigate, onNavigate] + [navigate] ); return { recentSessions, activeSessionId, - currentSessionId, fetchSessions, handleNavClick, - handleNewChat, handleSessionClick, }; } @@ -267,8 +192,3 @@ export function getSessionDisplayName(session: Session): string { } return session.name; } - -export function truncateMessage(msg?: string, maxLen = 20): string { - if (!msg) return 'New Chat'; - return msg.length > maxLen ? msg.substring(0, maxLen) + '...' : msg; -} diff --git a/ui/desktop/src/i18n/messages/en.json b/ui/desktop/src/i18n/messages/en.json index 9ba65c279d..8150b81a00 100644 --- a/ui/desktop/src/i18n/messages/en.json +++ b/ui/desktop/src/i18n/messages/en.json @@ -155,15 +155,6 @@ "bottomMenuExtensionSelection.searchExtensions": { "defaultMessage": "search extensions..." }, - "bottomMenuModeSelection.autoFallback": { - "defaultMessage": "auto" - }, - "bottomMenuModeSelection.automaticModeDescription": { - "defaultMessage": "Automatic mode selection" - }, - "bottomMenuModeSelection.currentModeTitle": { - "defaultMessage": "Current mode: {label} - {description}" - }, "cardButtons.configure": { "defaultMessage": "Configure" }, @@ -3809,9 +3800,6 @@ "sessionIndicators.streaming": { "defaultMessage": "Streaming" }, - "sessionItem.messageCount": { - "defaultMessage": "{count} messages" - }, "sessionSharingSection.alreadyConfigured": { "defaultMessage": "Session sharing has already been configured" }, diff --git a/ui/desktop/src/i18n/messages/ru.json b/ui/desktop/src/i18n/messages/ru.json index 85bf1005b7..808b499c52 100644 --- a/ui/desktop/src/i18n/messages/ru.json +++ b/ui/desktop/src/i18n/messages/ru.json @@ -95,15 +95,6 @@ "bottomMenuExtensionSelection.searchExtensions": { "defaultMessage": "поиск расширений..." }, - "bottomMenuModeSelection.autoFallback": { - "defaultMessage": "авто" - }, - "bottomMenuModeSelection.automaticModeDescription": { - "defaultMessage": "Автоматический выбор режима" - }, - "bottomMenuModeSelection.currentModeTitle": { - "defaultMessage": "Текущий режим: {label} - {description}" - }, "cardButtons.configure": { "defaultMessage": "Настроить" }, @@ -3728,9 +3719,6 @@ "sessionIndicators.streaming": { "defaultMessage": "Потоковая передача" }, - "sessionItem.messageCount": { - "defaultMessage": "Сообщений: {count}" - }, "sessionSharingSection.alreadyConfigured": { "defaultMessage": "Общий доступ к сеансам уже настроен" }, diff --git a/ui/desktop/src/i18n/messages/tr.json b/ui/desktop/src/i18n/messages/tr.json index 6e0720e456..ad2ea8c689 100644 --- a/ui/desktop/src/i18n/messages/tr.json +++ b/ui/desktop/src/i18n/messages/tr.json @@ -98,15 +98,6 @@ "bottomMenuExtensionSelection.searchExtensions": { "defaultMessage": "uzantıları ara..." }, - "bottomMenuModeSelection.autoFallback": { - "defaultMessage": "otomatik" - }, - "bottomMenuModeSelection.automaticModeDescription": { - "defaultMessage": "Otomatik mod seçimi" - }, - "bottomMenuModeSelection.currentModeTitle": { - "defaultMessage": "Geçerli mod: {label} - {description}" - }, "cardButtons.configure": { "defaultMessage": "Yapılandır" }, @@ -3842,9 +3833,6 @@ "sessionIndicators.streaming": { "defaultMessage": "Akış" }, - "sessionItem.messageCount": { - "defaultMessage": "{count} mesajları" - }, "sessionSharingSection.alreadyConfigured": { "defaultMessage": "Oturum paylaşımı zaten yapılandırılmış" }, diff --git a/ui/desktop/src/i18n/messages/zh-CN.json b/ui/desktop/src/i18n/messages/zh-CN.json index 6991e80e95..45677612ba 100644 --- a/ui/desktop/src/i18n/messages/zh-CN.json +++ b/ui/desktop/src/i18n/messages/zh-CN.json @@ -95,15 +95,6 @@ "bottomMenuExtensionSelection.searchExtensions": { "defaultMessage": "搜索扩展…" }, - "bottomMenuModeSelection.autoFallback": { - "defaultMessage": "自动" - }, - "bottomMenuModeSelection.automaticModeDescription": { - "defaultMessage": "自动模式选择" - }, - "bottomMenuModeSelection.currentModeTitle": { - "defaultMessage": "当前模式:{label} - {description}" - }, "cardButtons.configure": { "defaultMessage": "配置" }, @@ -3662,9 +3653,6 @@ "sessionIndicators.streaming": { "defaultMessage": "流式传输中" }, - "sessionItem.messageCount": { - "defaultMessage": "{count} 条消息" - }, "sessionSharingSection.alreadyConfigured": { "defaultMessage": "会话分享已配置" },