feat:添加对话情景功能
This commit is contained in:
@@ -760,6 +760,37 @@ body {
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ---- Scenario Quick Selector (chat header) ---- */
|
||||
.scenario-selector {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface-2);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 14px;
|
||||
padding: 4px 26px 4px 10px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: all var(--transition-fast);
|
||||
letter-spacing: 0.01em;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23888' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
}
|
||||
|
||||
.scenario-selector:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.scenario-selector:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.chat-panel-header__stats {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 400;
|
||||
@@ -877,6 +908,31 @@ body {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ---- System message (scenario switch, etc.) ---- */
|
||||
.chat-message--system {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 6px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chat-message--system::before,
|
||||
.chat-message--system::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.chat-message--system__text {
|
||||
font-size: 0.7rem;
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.chat-message__role {
|
||||
font-size: 0.62rem;
|
||||
font-weight: 600;
|
||||
@@ -1505,6 +1561,75 @@ body {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ---- Scenario Selection Cards ---- */
|
||||
|
||||
.scenario-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 0 24px;
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.scenario-cards__title {
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
font-weight: 600;
|
||||
padding: 0 4px 4px;
|
||||
}
|
||||
|
||||
.scenario-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface-2);
|
||||
border: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.scenario-card:hover {
|
||||
background: var(--color-surface-3);
|
||||
border-color: var(--color-primary);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.scenario-card:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.scenario-card__icon {
|
||||
font-size: 1.4rem;
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.scenario-card__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.scenario-card__title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.scenario-card__desc {
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ---- Voice Input Button ---- */
|
||||
|
||||
.chat-input__voice {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { AuthPage } from "./components/AuthPage";
|
||||
import { AuthProvider, useAuth } from "./lib/auth";
|
||||
import { loadConfig, loadTheme, saveTheme } from "./lib/storage";
|
||||
import { I18nContext, parseLocale, t } from "./lib/i18n";
|
||||
import { scenarios } from "./lib/scenarios";
|
||||
import type { Locale } from "./lib/i18n";
|
||||
import type { Theme } from "./types";
|
||||
import "./App.css";
|
||||
@@ -180,6 +181,27 @@ function AppContent() {
|
||||
sendTextMessage(prompt);
|
||||
}, [sendTextMessage]);
|
||||
|
||||
// ---- 情景选择 ----
|
||||
const handleSelectScenario = useCallback((scenarioId: string) => {
|
||||
const sc = scenarios.find(s => s.id === scenarioId);
|
||||
const updates: Partial<import("./types").SessionConfig> = { scenario: scenarioId };
|
||||
// 如果情景有默认语言,同步切换
|
||||
if (sc?.defaultLanguage) {
|
||||
updates.language = sc.defaultLanguage;
|
||||
}
|
||||
updateConfig(updates);
|
||||
// 插入系统提示消息
|
||||
const scenarioName = sc ? `${sc.icon} ${tr(sc.nameKey)}` : scenarioId;
|
||||
setMessages(prev => [...prev, {
|
||||
role: "system",
|
||||
content: tr("chat.scenarioSwitched").replace("{name}", scenarioName),
|
||||
timestamp: Date.now(),
|
||||
}]);
|
||||
}, [updateConfig, setMessages, tr]);
|
||||
|
||||
// 当前情景对象
|
||||
const activeScenario = scenarios.find(s => s.id === (config.scenario || "free_chat")) || scenarios[0];
|
||||
|
||||
// ---- 键盘快捷键 ----
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
@@ -443,6 +465,16 @@ function AppContent() {
|
||||
<div className="chat-panel-header">
|
||||
<span>{tr("chat.title")}</span>
|
||||
<div className="chat-panel-header__right">
|
||||
<select
|
||||
className="scenario-selector"
|
||||
value={activeScenario.id}
|
||||
onChange={(e) => handleSelectScenario(e.target.value)}
|
||||
title={tr("settings.scenario")}
|
||||
>
|
||||
{scenarios.map((sc) => (
|
||||
<option key={sc.id} value={sc.id}>{sc.icon} {tr(sc.nameKey)}</option>
|
||||
))}
|
||||
</select>
|
||||
{isConnected && mode === "observation" && (
|
||||
<span className="chat-panel-header__mode">{tr("chat.mode.observation")}</span>
|
||||
)}
|
||||
@@ -465,14 +497,16 @@ function AppContent() {
|
||||
messages={messages}
|
||||
currentReply={currentReply}
|
||||
connectionStatus={connectionStatus}
|
||||
currentScenario={config.scenario}
|
||||
isProcessing={isProcessing}
|
||||
isVADReady={isVADReady}
|
||||
vadError={vadError}
|
||||
vadError={vadError ?? undefined}
|
||||
isMicOn={isMicOn}
|
||||
isSpeaking={isSpeaking}
|
||||
onSendText={sendTextMessage}
|
||||
onToggleMic={toggleMic}
|
||||
onSceneCard={handleSceneCard}
|
||||
onSelectScenario={handleSelectScenario}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// ============================================================
|
||||
// ChatPanel — 消息展示面板
|
||||
// 职责:渲染对话消息列表、流式光标、元数据、自动滚动、文本输入
|
||||
// 增强:空状态场景卡片、语音输入按钮
|
||||
// 增强:空状态情景选择卡片、语音输入按钮
|
||||
// ============================================================
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useI18n } from "../../lib/i18n";
|
||||
import { scenarios } from "../../lib/scenarios";
|
||||
import type { ChatMessage } from "../../types";
|
||||
import type { ConnectionStatus } from "../../lib/websocket";
|
||||
|
||||
@@ -13,6 +14,7 @@ interface ChatPanelProps {
|
||||
messages: ChatMessage[];
|
||||
currentReply?: string;
|
||||
connectionStatus: ConnectionStatus;
|
||||
currentScenario?: string;
|
||||
isProcessing?: boolean;
|
||||
isVADReady?: boolean;
|
||||
vadError?: string;
|
||||
@@ -21,9 +23,10 @@ interface ChatPanelProps {
|
||||
onSendText?: (text: string) => void;
|
||||
onToggleMic?: () => void;
|
||||
onSceneCard?: (prompt: string) => void;
|
||||
onSelectScenario?: (scenarioId: string) => void;
|
||||
}
|
||||
|
||||
/** 场景卡片数据 */
|
||||
/** 场景卡片数据(视觉分析快捷) */
|
||||
function getSceneCards(t: (key: string) => string) {
|
||||
return [
|
||||
{ icon: "👁", titleKey: "scene.describe", descKey: "scene.describe.desc", prompt: t("scene.describe") },
|
||||
@@ -37,6 +40,7 @@ export function ChatPanel({
|
||||
messages,
|
||||
currentReply,
|
||||
connectionStatus,
|
||||
currentScenario,
|
||||
isProcessing,
|
||||
isVADReady,
|
||||
vadError,
|
||||
@@ -45,6 +49,7 @@ export function ChatPanel({
|
||||
onSendText,
|
||||
onToggleMic,
|
||||
onSceneCard,
|
||||
onSelectScenario,
|
||||
}: ChatPanelProps) {
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -55,6 +60,8 @@ export function ChatPanel({
|
||||
const isConnected = connectionStatus === "connected";
|
||||
const isEmpty = messages.length === 0 && !currentReply;
|
||||
const sceneCards = getSceneCards(t);
|
||||
const activeScenario = currentScenario || "free_chat";
|
||||
const isFreeChat = activeScenario === "free_chat";
|
||||
|
||||
// 用户上滚时暂停自动滚动,滚到底部时恢复
|
||||
useEffect(() => {
|
||||
@@ -97,46 +104,74 @@ export function ChatPanel({
|
||||
return (
|
||||
<div className="chat-panel">
|
||||
<div className="chat-panel__messages" ref={containerRef}>
|
||||
{/* 空状态:场景卡片 */}
|
||||
{/* 空状态:情景选择 + 场景卡片 */}
|
||||
{isEmpty && (
|
||||
<div className="chat-panel__welcome">
|
||||
<span className="chat-panel__welcome-icon">💬</span>
|
||||
<span className="chat-panel__welcome-icon">{scenarios.find(s => s.id === activeScenario)?.icon || "💬"}</span>
|
||||
<p>{isConnected ? t("chat.welcome.prompt") : t("chat.empty.prompt")}</p>
|
||||
<span className="chat-panel__welcome-hint">{isConnected ? t("chat.welcome.hint") : t("chat.empty.hint")}</span>
|
||||
|
||||
{/* 场景快捷卡片 */}
|
||||
<div className="scene-cards">
|
||||
{sceneCards.map((card) => (
|
||||
<button
|
||||
key={card.titleKey}
|
||||
className="scene-card"
|
||||
onClick={() => handleSceneCard(card.prompt)}
|
||||
>
|
||||
<span className="scene-card__icon">{card.icon}</span>
|
||||
<div className="scene-card__text">
|
||||
<span className="scene-card__title">{t(card.titleKey)}</span>
|
||||
<span className="scene-card__desc">{t(card.descKey)}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* 情景选择卡片(非 free_chat 时隐藏,因为已通过 header 切换) */}
|
||||
{isFreeChat && onSelectScenario && (
|
||||
<div className="scenario-cards">
|
||||
<div className="scenario-cards__title">{t("scenario.choose")}</div>
|
||||
{scenarios.filter(s => s.id !== "free_chat").map((sc) => (
|
||||
<button
|
||||
key={sc.id}
|
||||
className="scenario-card"
|
||||
onClick={() => onSelectScenario(sc.id)}
|
||||
>
|
||||
<span className="scenario-card__icon">{sc.icon}</span>
|
||||
<div className="scenario-card__text">
|
||||
<span className="scenario-card__title">{t(sc.nameKey)}</span>
|
||||
<span className="scenario-card__desc">{t(sc.descKey)}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 视觉分析快捷卡片(仅 free_chat 模式) */}
|
||||
{isFreeChat && isConnected && (
|
||||
<div className="scene-cards">
|
||||
{sceneCards.map((card) => (
|
||||
<button
|
||||
key={card.titleKey}
|
||||
className="scene-card"
|
||||
onClick={() => handleSceneCard(card.prompt)}
|
||||
>
|
||||
<span className="scene-card__icon">{card.icon}</span>
|
||||
<div className="scene-card__text">
|
||||
<span className="scene-card__title">{t(card.titleKey)}</span>
|
||||
<span className="scene-card__desc">{t(card.descKey)}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.map((msg, index) => (
|
||||
<div key={index} className={`chat-message chat-message--${msg.role}`}>
|
||||
<div className="chat-message__role">
|
||||
{msg.role === "user" ? t("chat.userLabel") : "AI"}
|
||||
msg.role === "system" ? (
|
||||
<div key={index} className="chat-message chat-message--system">
|
||||
<span className="chat-message--system__text">{msg.content}</span>
|
||||
</div>
|
||||
<div className="chat-message__content">{msg.content}</div>
|
||||
{msg.role === "assistant" && msg.tokensUsed !== undefined && (
|
||||
<div className="chat-message__meta">
|
||||
{msg.tokensUsed} tokens
|
||||
{msg.latencyMs !== undefined && ` · ${(msg.latencyMs / 1000).toFixed(1)}s`}
|
||||
{msg.model && ` · ${msg.model}`}
|
||||
) : (
|
||||
<div key={index} className={`chat-message chat-message--${msg.role}`}>
|
||||
<div className="chat-message__role">
|
||||
{msg.role === "user" ? t("chat.userLabel") : "AI"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="chat-message__content">{msg.content}</div>
|
||||
{msg.role === "assistant" && msg.tokensUsed !== undefined && (
|
||||
<div className="chat-message__meta">
|
||||
{msg.tokensUsed} tokens
|
||||
{msg.latencyMs !== undefined && ` · ${(msg.latencyMs / 1000).toFixed(1)}s`}
|
||||
{msg.model && ` · ${msg.model}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
|
||||
{/* 流式回复(尚未完成) */}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// ============================================================
|
||||
// ConfigPanel — 右侧抽屉式配置面板
|
||||
// 职责:主题切换、TTS 开关、detail level 切换、语言选择
|
||||
// 职责:主题切换、TTS 开关、detail level 切换、语言选择、情景选择
|
||||
// ============================================================
|
||||
|
||||
import { useI18n } from "../../lib/i18n";
|
||||
import { scenarios } from "../../lib/scenarios";
|
||||
import type { SessionConfig, Theme } from "../../types";
|
||||
|
||||
interface ConfigPanelProps {
|
||||
@@ -89,6 +90,21 @@ export function ConfigPanel({ config, theme, username, onUpdate, onThemeChange,
|
||||
<option value="ja-JP">日本語</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="config-row">
|
||||
<div className="config-row__info">
|
||||
<span className="config-row__label">{t("settings.scenario")}</span>
|
||||
<span className="config-row__desc">{t("settings.scenario.desc")}</span>
|
||||
</div>
|
||||
<select
|
||||
value={config.scenario || "free_chat"}
|
||||
onChange={(e) => onUpdate({ scenario: e.target.value })}
|
||||
>
|
||||
{scenarios.map((sc) => (
|
||||
<option key={sc.id} value={sc.id}>{sc.icon} {t(sc.nameKey)}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{username && onLogout && (
|
||||
|
||||
@@ -63,14 +63,6 @@ export function useSessionList() {
|
||||
saveSessionSummaries(updated);
|
||||
}, []);
|
||||
|
||||
/** 保存当前会话的消息历史 */
|
||||
const saveCurrentSession = useCallback((messages: ChatMessage[]) => {
|
||||
const id = sessionsRef.current.length > 0 ? sessionsRef.current[0].id : null;
|
||||
// 找到 activeSessionId 对应的会话
|
||||
// 这里不依赖 activeSessionId state,而是通过参数传入
|
||||
return messages;
|
||||
}, []);
|
||||
|
||||
/** 保存指定会话的消息并更新摘要 */
|
||||
const persistSession = useCallback((sessionId: string, messages: ChatMessage[]) => {
|
||||
if (!sessionId) return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// 来源:docs/03-接口文档.md §五 错误码
|
||||
// ============================================================
|
||||
|
||||
import type { ErrorCode } from "../types";
|
||||
// ErrorCode type reserved for future use
|
||||
|
||||
/** 将错误码转为用户友好文案(需要传入翻译函数) */
|
||||
export function getErrorMessage(code: string, translate: (key: string) => string): string {
|
||||
|
||||
@@ -26,6 +26,21 @@ export const enUS: TranslationMap = {
|
||||
"settings.detail.high": "High",
|
||||
"settings.language": "Language",
|
||||
"settings.language.desc": "Interaction language preference",
|
||||
"settings.scenario": "Scenario",
|
||||
"settings.scenario.desc": "Choose the AI's role",
|
||||
|
||||
// Scenarios
|
||||
"scenario.choose": "Choose a scenario",
|
||||
"scenario.freeChat": "Free Chat",
|
||||
"scenario.freeChat.desc": "AI visual assistant, chat freely",
|
||||
"scenario.interviewer": "Mock Interviewer",
|
||||
"scenario.interviewer.desc": "AI plays interviewer for practice",
|
||||
"scenario.englishTeacher": "English Tutor",
|
||||
"scenario.englishTeacher.desc": "AI English tutor, corrects grammar",
|
||||
"scenario.debate": "Debate Opponent",
|
||||
"scenario.debate.desc": "AI challenges your viewpoints",
|
||||
"scenario.interpreter": "Interpreter",
|
||||
"scenario.interpreter.desc": "Real-time Chinese-English translation",
|
||||
|
||||
// Video indicators
|
||||
"video.observing": "👁️ Observing",
|
||||
@@ -63,6 +78,7 @@ export const enUS: TranslationMap = {
|
||||
"chat.userLabel": "You",
|
||||
"chat.input.placeholder": "Type a message...",
|
||||
"chat.send": "Send",
|
||||
"chat.scenarioSwitched": "Switched to {name} mode",
|
||||
|
||||
// Session messages
|
||||
"session.changeDetected": "👁️ Scene change detected",
|
||||
|
||||
@@ -26,6 +26,21 @@ export const jaJP: TranslationMap = {
|
||||
"settings.detail.high": "高",
|
||||
"settings.language": "言語",
|
||||
"settings.language.desc": "インタラクション言語の設定",
|
||||
"settings.scenario": "シナリオ",
|
||||
"settings.scenario.desc": "AIの役割を選択",
|
||||
|
||||
// Scenarios
|
||||
"scenario.choose": "シナリオを選択",
|
||||
"scenario.freeChat": "フリーチャット",
|
||||
"scenario.freeChat.desc": "AIビジュアルアシスタントと自由に会話",
|
||||
"scenario.interviewer": "模擬面接官",
|
||||
"scenario.interviewer.desc": "AIが面接官として模擬面接を実施",
|
||||
"scenario.englishTeacher": "英語の先生",
|
||||
"scenario.englishTeacher.desc": "AI英語教師が文法を修正し会話を導く",
|
||||
"scenario.debate": "ディベート相手",
|
||||
"scenario.debate.desc": "AIがあなたの观点に反論し論理的思考を鍛える",
|
||||
"scenario.interpreter": "同時通訳",
|
||||
"scenario.interpreter.desc": "リアルタイムで中日相互翻訳",
|
||||
|
||||
// Video indicators
|
||||
"video.observing": "👁️ 観察中",
|
||||
@@ -63,6 +78,7 @@ export const jaJP: TranslationMap = {
|
||||
"chat.userLabel": "あなた",
|
||||
"chat.input.placeholder": "メッセージを入力...",
|
||||
"chat.send": "送信",
|
||||
"chat.scenarioSwitched": "{name} モードに切り替えました",
|
||||
|
||||
// Session messages
|
||||
"session.changeDetected": "👁️ シーン変化を検出",
|
||||
|
||||
@@ -26,6 +26,21 @@ export const zhCN: TranslationMap = {
|
||||
"settings.detail.high": "高",
|
||||
"settings.language": "语言",
|
||||
"settings.language.desc": "交互语言偏好",
|
||||
"settings.scenario": "对话情景",
|
||||
"settings.scenario.desc": "选择 AI 扮演的角色",
|
||||
|
||||
// Scenarios
|
||||
"scenario.choose": "选择对话情景",
|
||||
"scenario.freeChat": "自由对话",
|
||||
"scenario.freeChat.desc": "AI 视觉助手,自由聊天",
|
||||
"scenario.interviewer": "模拟面试官",
|
||||
"scenario.interviewer.desc": "AI 扮演面试官,进行模拟面试",
|
||||
"scenario.englishTeacher": "英语老师",
|
||||
"scenario.englishTeacher.desc": "AI 英语外教,纠正语法并引导对话",
|
||||
"scenario.debate": "辩论对手",
|
||||
"scenario.debate.desc": "AI 反驳你的观点,锻炼逻辑思维",
|
||||
"scenario.interpreter": "同声翻译",
|
||||
"scenario.interpreter.desc": "实时中英互译,口语化翻译",
|
||||
|
||||
// Video indicators
|
||||
"video.observing": "👁️ 观察中",
|
||||
@@ -63,6 +78,7 @@ export const zhCN: TranslationMap = {
|
||||
"chat.userLabel": "你",
|
||||
"chat.input.placeholder": "输入文字对话...",
|
||||
"chat.send": "发送",
|
||||
"chat.scenarioSwitched": "已切换到 {name} 模式",
|
||||
|
||||
// Session messages
|
||||
"session.changeDetected": "👁️ 画面变化检测",
|
||||
|
||||
47
frontend/src/lib/scenarios.ts
Normal file
47
frontend/src/lib/scenarios.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// ============================================================
|
||||
// scenarios — 对话情景定义
|
||||
// 职责:定义预置情景的展示数据和交互行为
|
||||
// 注意:system prompt 由后端 scenarios.go 管理,此处仅定义 UI 展示数据
|
||||
// ============================================================
|
||||
|
||||
export interface Scenario {
|
||||
id: string;
|
||||
icon: string;
|
||||
nameKey: string; // i18n key
|
||||
descKey: string; // i18n key
|
||||
defaultLanguage?: string; // 可选:切换情景时自动设置的语言
|
||||
}
|
||||
|
||||
export const scenarios: Scenario[] = [
|
||||
{
|
||||
id: "free_chat",
|
||||
icon: "💬",
|
||||
nameKey: "scenario.freeChat",
|
||||
descKey: "scenario.freeChat.desc",
|
||||
},
|
||||
{
|
||||
id: "interviewer",
|
||||
icon: "🎯",
|
||||
nameKey: "scenario.interviewer",
|
||||
descKey: "scenario.interviewer.desc",
|
||||
},
|
||||
{
|
||||
id: "english_teacher",
|
||||
icon: "📚",
|
||||
nameKey: "scenario.englishTeacher",
|
||||
descKey: "scenario.englishTeacher.desc",
|
||||
defaultLanguage: "en-US",
|
||||
},
|
||||
{
|
||||
id: "debate",
|
||||
icon: "⚔️",
|
||||
nameKey: "scenario.debate",
|
||||
descKey: "scenario.debate.desc",
|
||||
},
|
||||
{
|
||||
id: "interpreter",
|
||||
icon: "🌐",
|
||||
nameKey: "scenario.interpreter",
|
||||
descKey: "scenario.interpreter.desc",
|
||||
},
|
||||
];
|
||||
@@ -15,6 +15,7 @@ const DEFAULT_CONFIG: SessionConfig = {
|
||||
ttsEnabled: true,
|
||||
detailLevel: "low",
|
||||
language: "zh-CN",
|
||||
scenario: "free_chat",
|
||||
};
|
||||
|
||||
/** 加载配置,无存储时返回默认值 */
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface SessionConfig {
|
||||
ttsEnabled: boolean;
|
||||
detailLevel: "low" | "high";
|
||||
language: string;
|
||||
scenario: string; // 情景 ID,如 "free_chat"、"interviewer"
|
||||
}
|
||||
|
||||
export type Theme = "dark" | "light";
|
||||
@@ -32,7 +33,7 @@ export interface SessionSummary {
|
||||
// ---- 聊天消息 ----
|
||||
|
||||
export interface ChatMessage {
|
||||
role: "user" | "assistant";
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
imageUrl?: string;
|
||||
timestamp: number;
|
||||
|
||||
Reference in New Issue
Block a user