diff --git a/frontend/src/App.css b/frontend/src/App.css index 9e15e6e..8cdd197 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -241,17 +241,20 @@ body { border-right: 1px solid var(--color-border); overflow: hidden; box-shadow: 8px 0 40px rgba(10, 10, 15, 0.4); - transition: transform 0.2s ease-out; + transition: transform 0.2s ease-out, visibility 0s linear 0.2s; } .sidebar--collapsed { transform: translateX(-100%); pointer-events: none; + visibility: hidden; } .sidebar--open { transform: translateX(0); pointer-events: auto; + visibility: visible; + transition: transform 0.2s ease-out, visibility 0s linear 0s; } .sidebar__header { @@ -912,6 +915,8 @@ body { gap: 10px; padding: 6px 0; background: transparent; + max-width: 100%; + align-self: center; } .chat-message--system::before, @@ -1389,6 +1394,11 @@ body { to { opacity: 1; } } +@keyframes slideUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + /* ---- Scrollbar ---- */ ::-webkit-scrollbar { @@ -2203,7 +2213,7 @@ body { } /* ============================================================ - Custom Scenarios Styles + Custom Scenarios Styles — 白色商务风格(使用 CSS 变量) ============================================================ */ /* Scenario list */ @@ -2219,16 +2229,16 @@ body { align-items: center; gap: 10px; padding: 10px 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 8px; + background: var(--color-surface-2); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-fast); } .scenario-item:hover { - background: rgba(255, 255, 255, 0.06); - border-color: rgba(255, 255, 255, 0.15); + background: var(--color-surface-3); + border-color: var(--color-surface-3); } .scenario-item input[type="radio"] { @@ -2243,12 +2253,12 @@ body { .scenario-item__name { flex: 1; font-size: 14px; - color: rgba(255, 255, 255, 0.9); + color: var(--color-text); } .scenario-item__desc { font-size: 12px; - color: rgba(255, 255, 255, 0.5); + color: var(--color-text-muted); margin-top: 2px; } @@ -2281,28 +2291,31 @@ body { .scenario-action-btn { padding: 4px 8px; background: transparent; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 4px; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); font-size: 14px; + color: var(--color-text-muted); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-fast); } .scenario-action-btn:hover { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); + background: var(--color-surface-2); + border-color: var(--color-surface-3); + color: var(--color-text); } .scenario-action-btn--confirm { - background: rgba(248, 113, 113, 0.15); - border-color: rgba(248, 113, 113, 0.3); + background: rgba(248, 113, 113, 0.1); + border-color: rgba(248, 113, 113, 0.2); + color: var(--color-error); } .scenario-empty { padding: 16px; text-align: center; font-size: 13px; - color: rgba(255, 255, 255, 0.4); + color: var(--color-text-muted); font-style: italic; } @@ -2311,17 +2324,17 @@ body { margin-left: auto; padding: 4px 12px; font-size: 12px; - background: rgba(196, 97, 47, 0.15); - border: 1px solid rgba(196, 97, 47, 0.3); + background: rgba(59, 130, 246, 0.08); + border: 1px solid rgba(59, 130, 246, 0.2); border-radius: 999px; - color: #C4612F; + color: var(--color-primary); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-fast); } .config-create-btn:hover { - background: rgba(196, 97, 47, 0.25); - border-color: rgba(196, 97, 47, 0.5); + background: rgba(59, 130, 246, 0.16); + border-color: rgba(59, 130, 246, 0.4); } .config-group__title { @@ -2337,25 +2350,30 @@ body { left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.7); + background: rgba(10, 10, 15, 0.45); display: flex; align-items: center; justify-content: center; z-index: 10000; backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); animation: fadeIn 0.2s; } +[data-theme="light"] .modal-overlay { + background: rgba(0, 0, 0, 0.3); +} + .modal { - background: #1F2421; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 12px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); width: 90%; max-width: 600px; max-height: 85vh; display: flex; flex-direction: column; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + box-shadow: 0 16px 48px rgba(10, 10, 15, 0.25); animation: slideUp 0.3s; } @@ -2368,20 +2386,21 @@ body { align-items: center; justify-content: space-between; padding: 20px 24px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + border-bottom: 1px solid var(--color-border); } .modal__title { font-size: 18px; - font-weight: 500; - color: rgba(255, 255, 255, 0.95); + font-weight: 600; + color: var(--color-text); + letter-spacing: -0.01em; } .modal__close { background: transparent; border: none; - font-size: 24px; - color: rgba(255, 255, 255, 0.6); + font-size: 20px; + color: var(--color-text-muted); cursor: pointer; padding: 0; width: 32px; @@ -2389,13 +2408,13 @@ body { display: flex; align-items: center; justify-content: center; - border-radius: 6px; - transition: all 0.2s; + border-radius: var(--radius-sm); + transition: all var(--transition-fast); } .modal__close:hover { - background: rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.9); + background: var(--color-surface-2); + color: var(--color-text); } .modal__body { @@ -2409,7 +2428,7 @@ body { gap: 12px; justify-content: flex-end; padding-top: 16px; - border-top: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid var(--color-border); margin-top: 8px; } @@ -2424,12 +2443,12 @@ body { gap: 8px; font-size: 14px; font-weight: 500; - color: rgba(255, 255, 255, 0.85); + color: var(--color-text); margin-bottom: 8px; } .form-required { - color: #C4612F; + color: var(--color-primary); } .form-input, @@ -2437,21 +2456,28 @@ body { .form-select { width: 100%; padding: 10px 12px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 6px; - color: rgba(255, 255, 255, 0.9); + background: var(--color-surface-2); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + color: var(--color-text); font-size: 14px; font-family: inherit; - transition: all 0.2s; + transition: all var(--transition-fast); +} + +.form-input::placeholder, +.form-textarea::placeholder { + color: var(--color-text-muted); + opacity: 0.7; } .form-input:focus, .form-textarea:focus, .form-select:focus { outline: none; - border-color: rgba(196, 97, 47, 0.5); - background: rgba(255, 255, 255, 0.08); + border-color: var(--color-primary); + background: var(--color-surface); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .form-textarea { @@ -2465,15 +2491,15 @@ body { display: block; margin-top: 4px; font-size: 12px; - color: rgba(255, 255, 255, 0.4); + color: var(--color-text-muted); } .form-error { padding: 12px; - background: rgba(248, 113, 113, 0.15); - border: 1px solid rgba(248, 113, 113, 0.3); - border-radius: 6px; - color: #f87171; + background: rgba(248, 113, 113, 0.08); + border: 1px solid rgba(248, 113, 113, 0.2); + border-radius: var(--radius-sm); + color: var(--color-error); font-size: 13px; margin-top: 12px; } @@ -2493,129 +2519,112 @@ body { align-items: center; justify-content: center; font-size: 22px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 8px; + background: var(--color-surface-2); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-fast); } .icon-picker__item:hover { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); + background: var(--color-surface-3); + border-color: var(--color-surface-3); transform: scale(1.05); } .icon-picker__item--active { - background: rgba(196, 97, 47, 0.2); - border-color: rgba(196, 97, 47, 0.5); + background: rgba(59, 130, 246, 0.1); + border-color: rgba(59, 130, 246, 0.4); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15); } /* Prompt guide */ .form-guide-btn { background: transparent; border: none; - color: #C4612F; + color: var(--color-primary); font-size: 12px; cursor: pointer; padding: 4px 8px; - border-radius: 4px; + border-radius: var(--radius-sm); margin-left: auto; - transition: all 0.2s; + transition: all var(--transition-fast); } .form-guide-btn:hover { - background: rgba(196, 97, 47, 0.1); + background: rgba(59, 130, 246, 0.06); } .form-guide { - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 8px; + background: var(--color-surface-2); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); padding: 16px; margin-bottom: 12px; font-size: 13px; line-height: 1.6; + color: var(--color-text); } .form-guide strong { - color: rgba(255, 255, 255, 0.9); + color: var(--color-text); } .form-guide ul { margin: 8px 0; padding-left: 20px; - color: rgba(255, 255, 255, 0.7); + color: var(--color-text-muted); } .form-guide__code { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 6px; + background: var(--color-surface-3); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); padding: 12px; margin-top: 8px; overflow-x: auto; font-size: 12px; line-height: 1.5; - color: rgba(255, 255, 255, 0.8); + color: var(--color-text); } -/* Buttons */ -.btn { +/* Buttons (modal-scoped overrides — only affect modal context) */ +.modal .btn { padding: 10px 20px; - border-radius: 6px; + border-radius: var(--radius-sm); font-size: 14px; - font-weight: 500; + font-weight: 600; cursor: pointer; - transition: all 0.2s; + transition: all var(--transition-fast); border: 1px solid transparent; } -.btn:disabled { +.modal .btn:disabled { opacity: 0.5; cursor: not-allowed; } -.btn--primary { - background: #C4612F; +.modal .btn--primary { + background: var(--color-primary); color: white; - border-color: #C4612F; + border-color: var(--color-primary); } -.btn--primary:hover:not(:disabled) { - background: #A94E22; - border-color: #A94E22; +.modal .btn--primary:hover:not(:disabled) { + background: var(--color-primary-hover); + border-color: var(--color-primary-hover); } -.btn--secondary { - background: transparent; - color: rgba(255, 255, 255, 0.7); - border-color: rgba(255, 255, 255, 0.2); +.modal .btn--secondary { + background: var(--color-surface-2); + color: var(--color-text-muted); + border-color: var(--color-border); } -.btn--secondary:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.3); - color: rgba(255, 255, 255, 0.9); +.modal .btn--secondary:hover:not(:disabled) { + background: var(--color-surface-3); + border-color: var(--color-surface-3); + color: var(--color-text); } -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes slideUp { - from { - transform: translateY(20px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} +/* Animations — fadeIn & slideUp defined in main animation section above */ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 198792e..8301a1d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,6 +21,15 @@ import { loadConfig, loadTheme, saveTheme } from "./lib/storage"; import { I18nContext, parseLocale, t } from "./lib/i18n"; import { renderScenarioIconById } from "./lib/scenarioIcons"; import type { Locale } from "./lib/i18n"; + +/** SVG 图标 ID → emoji 映射(用于纯文本场景,如系统消息) */ +const ICON_ID_TO_EMOJI: Record = { + sparkles: "✨", theater: "🎭", palette: "🎨", target: "🎯", film: "🎬", + "book-open": "📖", book: "📚", edit: "✏️", clipboard: "📋", "map-pin": "📍", + search: "🔍", lightbulb: "💡", briefcase: "💼", code: "💻", "graduation-cap": "🎓", + mic: "🎤", music: "🎵", globe: "🌐", heart: "❤️", shield: "🛡️", + zap: "⚡", coffee: "☕", tool: "🔧", "message-circle": "💬", +}; import type { Theme } from "./types"; import type { UserScenario } from "./lib/api/scenarios"; import "./App.css"; @@ -228,10 +237,14 @@ function AppContent() { } } updateConfig(updates); - // 插入系统提示消息 - const scenarioName = sc - ? `${sc.icon} ${sc.nameKey ? tr(sc.nameKey) : sc.name}` + // 插入系统提示消息(自建情景的 icon 是 SVG ID,需转为 emoji) + const iconText = sc + ? (ICON_ID_TO_EMOJI[sc.icon] || sc.icon) + : ""; + const displayName = sc + ? (sc.nameKey ? tr(sc.nameKey) : sc.name) : scenarioId; + const scenarioName = iconText ? `${iconText} ${displayName}` : displayName; setMessages(prev => [...prev, { id: uuidv4(), role: "system", @@ -607,6 +620,7 @@ function AppContent() { currentReply={currentReply} connectionStatus={connectionStatus} currentScenario={config.scenario} + allScenarios={allScenarios} isProcessing={isProcessing} isVADReady={isVADReady} vadError={vadError ?? undefined} diff --git a/frontend/src/components/ChatPanel/index.tsx b/frontend/src/components/ChatPanel/index.tsx index 741b825..1fa1f26 100644 --- a/frontend/src/components/ChatPanel/index.tsx +++ b/frontend/src/components/ChatPanel/index.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"; import { useI18n } from "../../lib/i18n"; import { scenarios } from "../../lib/scenarios"; +import type { ExtendedScenario } from "../../hooks/useScenarios"; import type { ChatMessage } from "../../types"; import type { ConnectionStatus } from "../../lib/websocket"; @@ -15,6 +16,7 @@ interface ChatPanelProps { currentReply?: string; connectionStatus: ConnectionStatus; currentScenario?: string; + allScenarios?: ExtendedScenario[]; isProcessing?: boolean; isVADReady?: boolean; vadError?: string | null; @@ -43,6 +45,7 @@ export function ChatPanel({ currentReply, connectionStatus, currentScenario, + allScenarios, isProcessing, isVADReady, vadError, @@ -103,6 +106,10 @@ export function ChatPanel({ } }; + // 当前情景对象(优先从 allScenarios 查找,含自建情景;兜底用系统情景) + const scenarioList = allScenarios || scenarios; + const activeScenarioObj = scenarioList.find(sc => sc.id === activeScenario); + return (
@@ -111,11 +118,13 @@ export function ChatPanel({
- {scenarios.find(s => s.id === activeScenario)?.icon} + {activeScenarioObj?.icon}
- {t(scenarios.find(s => s.id === activeScenario)?.nameKey || "")} -

{t(`scenario.${activeScenario}.hint`)}

+ {activeScenarioObj ? (activeScenarioObj.nameKey ? t(activeScenarioObj.nameKey) : activeScenarioObj.name) : ""} + {activeScenarioObj && (activeScenarioObj.descKey || activeScenarioObj.description) && ( +

{activeScenarioObj.descKey ? t(activeScenarioObj.descKey) : activeScenarioObj.description}

+ )}
@@ -124,7 +133,7 @@ export function ChatPanel({ {/* 空状态:欢迎信息 */} {isEmpty && (
- {scenarios.find(s => s.id === activeScenario)?.icon || } + {activeScenarioObj?.icon || }

{isConnected ? t("chat.welcome.prompt") : t("chat.empty.prompt")}

{isConnected ? t("chat.welcome.hint") : t("chat.empty.hint")}