diff --git a/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx b/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx index b6478bbe..a1eca72e 100644 --- a/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx +++ b/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx @@ -58,6 +58,10 @@ function StringRenderer({ value, className }: { value: string; className?: strin } function ArrayRenderer({ value, className }: { value: unknown[]; className?: string }) { + if (!Array.isArray(value)) { + return ; + } + if (value.length === 0) { return Empty; } @@ -98,6 +102,10 @@ function ObjectRenderer({ value, className }: { value: Record; } function FilesRenderer({ value, className }: { value: Array<{ path: string }>; className?: string }) { + if (!Array.isArray(value)) { + return ; + } + if (value.length === 0) { return No files; } @@ -118,6 +126,10 @@ function FilesRenderer({ value, className }: { value: Array<{ path: string }>; c } function TagsRenderer({ value, className }: { value: string[]; className?: string }) { + if (!Array.isArray(value)) { + return ; + } + if (value.length === 0) { return No tags; } diff --git a/ccw/frontend/src/pages/ApiSettingsPage.tsx b/ccw/frontend/src/pages/ApiSettingsPage.tsx index e94c65df..1e009579 100644 --- a/ccw/frontend/src/pages/ApiSettingsPage.tsx +++ b/ccw/frontend/src/pages/ApiSettingsPage.tsx @@ -10,7 +10,7 @@ import { RefreshCw, } from 'lucide-react'; import { Button } from '@/components/ui/Button'; -import { TabsNavigation, type TabItem } from '@/components/ui/TabsNavigation'; +import { TabsNavigation } from '@/components/ui/TabsNavigation'; import { ProviderList, ProviderModal, @@ -25,7 +25,7 @@ import { ManageModelsModal, } from '@/components/api-settings'; import { ConfigSync } from '@/components/shared'; -import { useProviders, useEndpoints, useModelPools, useCliSettings } from '@/hooks/useApiSettings'; +import { useProviders, useEndpoints, useModelPools, useCliSettings, useSyncApiConfig } from '@/hooks/useApiSettings'; import { useNotifications } from '@/hooks/useNotifications'; // Tab type definitions @@ -35,6 +35,7 @@ export function ApiSettingsPage() { const { formatMessage } = useIntl(); const { success, error } = useNotifications(); const [activeTab, setActiveTab] = useState('providers'); + const syncMutation = useSyncApiConfig(); // Get providers, endpoints, model pools, and CLI settings data const { providers } = useProviders(); @@ -170,10 +171,19 @@ export function ApiSettingsPage() { // Sync to CodexLens handler const handleSyncToCodexLens = async (providerId: string) => { + const providerName = providers.find((p) => p.id === providerId)?.name ?? providerId; + try { - // TODO: Implement actual sync API call - // For now, just show a success message - success(formatMessage({ id: 'apiSettings.messages.configSynced' })); + const result = await syncMutation.mutateAsync(); + const messageParts = [ + providerName, + result.yamlPath ?? result.message, + ].filter(Boolean); + + success( + formatMessage({ id: 'apiSettings.messages.configSynced' }), + messageParts.length > 0 ? messageParts.join('\n') : undefined + ); } catch (err) { error(formatMessage({ id: 'apiSettings.providers.saveError' })); } diff --git a/ccw/frontend/src/pages/SettingsPage.tsx b/ccw/frontend/src/pages/SettingsPage.tsx index 7de1e903..ab29649d 100644 --- a/ccw/frontend/src/pages/SettingsPage.tsx +++ b/ccw/frontend/src/pages/SettingsPage.tsx @@ -25,6 +25,7 @@ import { Package, Home, Folder, + FolderOpen, Calendar, File, ArrowUpCircle, @@ -36,6 +37,7 @@ import { Input } from '@/components/ui/Input'; import { Badge } from '@/components/ui/Badge'; import { ThemeSelector } from '@/components/shared/ThemeSelector'; import { useTheme } from '@/hooks'; +import { toast } from 'sonner'; import { useConfigStore, selectCliTools, selectDefaultCliTool, selectUserPreferences } from '@/stores/configStore'; import type { CliToolConfig, UserPreferences } from '@/types/store'; import { cn } from '@/lib/utils'; @@ -60,9 +62,6 @@ import { const ENV_FILE_TOOLS = new Set(['gemini', 'qwen', 'opencode']); /** Tools that use --settings for Claude CLI settings file */ const SETTINGS_FILE_TOOLS = new Set(['claude']); -/** Tools that don't need any config file */ -const NO_CONFIG_FILE_TOOLS = new Set(['codex']); - function getConfigFileType(toolId: string): 'envFile' | 'settingsFile' | 'none' { if (ENV_FILE_TOOLS.has(toolId)) return 'envFile'; if (SETTINGS_FILE_TOOLS.has(toolId)) return 'settingsFile'; @@ -874,18 +873,13 @@ export function SettingsPage() { throw new Error(`HTTP ${res.status}`); } - // Show success notification via a brief visual indicator - const toast = document.createElement('div'); - toast.className = 'fixed bottom-4 right-4 z-50 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2'; - toast.textContent = formatMessage({ id: 'settings.cliTools.configSaved' }); - document.body.appendChild(toast); - setTimeout(() => toast.remove(), 3000); + toast.success(formatMessage({ id: 'settings.cliTools.configSaved' }), { + description: toolId, + }); } catch { - const toast = document.createElement('div'); - toast.className = 'fixed bottom-4 right-4 z-50 bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2'; - toast.textContent = formatMessage({ id: 'settings.cliTools.configSaveError' }); - document.body.appendChild(toast); - setTimeout(() => toast.remove(), 4000); + toast.error(formatMessage({ id: 'settings.cliTools.configSaveError' }), { + description: toolId, + }); } finally { setSavingTools((prev) => { const next = new Set(prev); diff --git a/ccw/frontend/src/stores/configStore.ts b/ccw/frontend/src/stores/configStore.ts index f323f9a9..fa026fc4 100644 --- a/ccw/frontend/src/stores/configStore.ts +++ b/ccw/frontend/src/stores/configStore.ts @@ -43,6 +43,13 @@ const defaultCliTools: Record = { tags: [], type: 'builtin', }, + opencode: { + enabled: true, + primaryModel: 'opencode/glm-4.7-free', + secondaryModel: 'opencode/glm-4.7-free', + tags: [], + type: 'builtin', + }, }; // Default API endpoints