diff --git a/.gitignore b/.gitignore index 2427d71..ee73c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ Thumbs.db # ---- Obsidian ---- .obsidian/ -.claudian/sessions/conv-1781943335504-q62bzosye.meta.json +.claudian/ +修改过程笔记/ +学习复盘/ diff --git a/frontend/src/App.css b/frontend/src/App.css index e4e9938..c5fe341 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -495,7 +495,10 @@ body { display: flex; flex-direction: column; padding: 16px 20px; - gap: 10px; + gap: 12px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--color-border) transparent; } .video-container { @@ -652,7 +655,7 @@ body { flex-direction: column; align-items: center; gap: 10px; - padding: 8px 0; + padding: 10px 0 4px; } .video-controls__row { @@ -1387,11 +1390,228 @@ body { .video-controls__toolbar { display: flex; - gap: 6px; + gap: 8px; flex-wrap: wrap; justify-content: center; + align-items: center; } +/* ---- Round Control Buttons (Camera / Mic) ---- */ + +.btn-ctrl-round { + width: 44px; + height: 44px; + border-radius: 50%; + border: 2px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); + flex-shrink: 0; + position: relative; +} + +.btn-ctrl-round:hover { + background: var(--color-surface-3); + border-color: var(--color-surface-3); + color: var(--color-text); + transform: scale(1.06); +} + +.btn-ctrl-round:active { + transform: scale(0.95); +} + +.btn-ctrl-round--on { + background: rgba(52, 211, 153, 0.1); + border-color: rgba(52, 211, 153, 0.3); + color: var(--color-success); +} + +.btn-ctrl-round--on:hover { + background: rgba(52, 211, 153, 0.18); + border-color: rgba(52, 211, 153, 0.45); +} + +.btn-ctrl-round--off { + background: rgba(248, 113, 113, 0.08); + border-color: rgba(248, 113, 113, 0.2); + color: var(--color-error); +} + +.btn-ctrl-round--off:hover { + background: rgba(248, 113, 113, 0.15); + border-color: rgba(248, 113, 113, 0.35); +} + +.btn-ctrl-round--speaking { + animation: micPulse 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite; + box-shadow: 0 0 14px rgba(52, 211, 153, 0.45); +} + +/* ---- Pill Control Buttons (Recognize / Interrupt / Stop) ---- */ + +.btn-ctrl-pill { + padding: 8px 16px; + border-radius: 22px; + font-size: 0.78rem; + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); + display: inline-flex; + align-items: center; + gap: 5px; + border: 1px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + white-space: nowrap; +} + +.btn-ctrl-pill:hover:not(:disabled) { + background: var(--color-surface-3); + color: var(--color-text); + border-color: var(--color-surface-3); +} + +.btn-ctrl-pill:active:not(:disabled) { + transform: translateY(1px); +} + +.btn-ctrl-pill:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-ctrl-pill--recognize { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); + border-color: rgba(59, 130, 246, 0.25); +} + +.btn-ctrl-pill--recognize:hover:not(:disabled) { + background: rgba(59, 130, 246, 0.2); + border-color: rgba(59, 130, 246, 0.4); +} + +.btn-ctrl-pill--interrupt { + background: rgba(251, 191, 36, 0.1); + color: var(--color-warning); + border-color: rgba(251, 191, 36, 0.25); +} + +.btn-ctrl-pill--interrupt:hover:not(:disabled) { + background: rgba(251, 191, 36, 0.2); + border-color: rgba(251, 191, 36, 0.4); +} + +.btn-ctrl-pill--stop { + background: rgba(248, 113, 113, 0.1); + color: var(--color-error); + border-color: rgba(248, 113, 113, 0.2); +} + +.btn-ctrl-pill--stop:hover:not(:disabled) { + background: rgba(248, 113, 113, 0.18); + border-color: rgba(248, 113, 113, 0.35); +} + +/* ---- Scenario Picker Grid (video panel) ---- */ + +.scenario-picker { + display: flex; + flex-direction: column; + gap: 8px; +} + +.scenario-picker__title { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 2px; +} + +.scenario-picker__label { + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-text-muted); + opacity: 0.7; +} + +.scenario-picker__active { + font-size: 0.7rem; + color: var(--color-primary); + font-weight: 500; +} + +.scenario-picker__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; +} + +.scenario-picker__card { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + padding: 10px 4px; + border-radius: var(--radius-sm); + border: 1px solid var(--color-border); + background: var(--color-surface-2); + color: var(--color-text-muted); + cursor: pointer; + transition: all var(--transition-fast); + text-align: center; + min-width: 0; +} + +.scenario-picker__card:hover { + background: var(--color-surface-3); + border-color: var(--color-surface-3); + color: var(--color-text); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(10, 10, 15, 0.15); +} + +.scenario-picker__card:active { + transform: translateY(0); +} + +.scenario-picker__card--active { + background: rgba(59, 130, 246, 0.08); + border-color: rgba(59, 130, 246, 0.35); + color: var(--color-primary); +} + +.scenario-picker__card--active:hover { + background: rgba(59, 130, 246, 0.14); + border-color: rgba(59, 130, 246, 0.45); + color: var(--color-primary); +} + +.scenario-picker__card-icon { + font-size: 1.3rem; + line-height: 1; +} + +.scenario-picker__card-name { + font-size: 0.68rem; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + letter-spacing: 0.01em; +} + +/* ---- Legacy button styles kept for backward compat ---- */ + .btn--recognize { background: rgba(59, 130, 246, 0.1); color: var(--color-primary); @@ -1416,35 +1636,37 @@ body { /* Device selectors */ .video-controls__devices { display: flex; - gap: 12px; + gap: 8px; justify-content: center; + width: 100%; } .device-select-wrapper { display: flex; - flex-direction: column; - gap: 4px; align-items: center; + gap: 6px; + flex: 1; + min-width: 0; } .device-select-label { - font-size: 0.65rem; + font-size: 0.72rem; color: var(--color-text-muted); - font-weight: 500; - letter-spacing: 0.01em; + flex-shrink: 0; } .device-select { - padding: 5px 10px; - font-size: 0.72rem; - border-radius: var(--radius-sm); + flex: 1; + min-width: 0; + padding: 5px 8px; + font-size: 0.7rem; + border-radius: 6px; border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-text-muted); cursor: pointer; transition: all var(--transition-fast); - min-width: 120px; - text-align: center; + text-overflow: ellipsis; } .device-select:hover { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 89e84d6..09b1910 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -401,6 +401,27 @@ function AppContent() { )} + {/* ---- 情景选择卡片网格(视频下方) ---- */} +
+
+ {tr("settings.scenario")} + {activeScenario.icon} {activeScenario.nameKey ? tr(activeScenario.nameKey) : activeScenario.name} +
+
+ {allScenarios.map((sc) => ( + + ))} +
+
+ {/* 视频下方控制区(三层结构) */}
{!isConnected ? ( @@ -415,33 +436,68 @@ function AppContent() { {/* 视频通话态:核心控制工具栏 */}
- {/* 识别画面按钮 */} {isProcessing && ( - )} -
@@ -502,18 +558,6 @@ function AppContent() {
{tr("chat.title")}
- {isConnected && stats.queryCount > 0 && ( {stats.queryCount} {tr("statusbar.recognitions")} diff --git a/frontend/src/components/ChatPanel/index.tsx b/frontend/src/components/ChatPanel/index.tsx index a0e2d8c..d213195 100644 --- a/frontend/src/components/ChatPanel/index.tsx +++ b/frontend/src/components/ChatPanel/index.tsx @@ -121,34 +121,14 @@ export function ChatPanel({
)} - {/* 空状态:情景选择 + 场景卡片 */} + {/* 空状态:欢迎信息 */} {isEmpty && (
{scenarios.find(s => s.id === activeScenario)?.icon || "💬"}

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

{isConnected ? t("chat.welcome.hint") : t("chat.empty.hint")} - {/* 情景选择卡片(非 free_chat 时隐藏,因为已通过 header 切换) */} - {isFreeChat && onSelectScenario && ( -
-
{t("scenario.choose")}
- {scenarios.filter(s => s.id !== "free_chat").map((sc) => ( - - ))} -
- )} - - {/* 视觉分析快捷卡片(仅 free_chat 模式) */} + {/* 视觉分析快捷卡片(仅 free_chat 模式 + 已连接) */} {isFreeChat && isConnected && (
{sceneCards.map((card) => (