- 新增 AuthContext/AuthProvider:登录/注册状态管理,JWT 自动刷新 - 新增 AuthPage 组件:登录/注册表单,支持模式切换和前端校验 - 新增 api.ts:封装 auth REST API 客户端(register/login/refresh/logout) - WebSocket 连接时拼接 ?token=<jwt>,重连自动携带 - useVisionSession 接受 accessToken 参数并透传 - App.tsx 包裹 AuthProvider,未登录时显示登录页 - storage.ts 新增 token/user 的 localStorage 存储 - i18n 新增中/英/日三语 auth 翻译 - App.css 新增 auth 页面和用户 badge 样式
173 lines
4.4 KiB
TypeScript
173 lines
4.4 KiB
TypeScript
// ============================================================
|
|
// Storage — localStorage 封装
|
|
// 职责:会话配置持久化
|
|
// ============================================================
|
|
|
|
import type { ChatMessage, SessionConfig, SessionSummary, Theme } from "../types";
|
|
|
|
const CONFIG_KEY = "camtalk:config";
|
|
const THEME_KEY = "camtalk:theme";
|
|
const ACCESS_TOKEN_KEY = "camtalk:access_token";
|
|
const REFRESH_TOKEN_KEY = "camtalk:refresh_token";
|
|
const USER_KEY = "camtalk:user";
|
|
|
|
const DEFAULT_CONFIG: SessionConfig = {
|
|
ttsEnabled: true,
|
|
detailLevel: "low",
|
|
language: "zh-CN",
|
|
};
|
|
|
|
/** 加载配置,无存储时返回默认值 */
|
|
export function loadConfig(): SessionConfig {
|
|
try {
|
|
const raw = localStorage.getItem(CONFIG_KEY);
|
|
if (!raw) return DEFAULT_CONFIG;
|
|
const parsed = JSON.parse(raw) as Partial<SessionConfig>;
|
|
return { ...DEFAULT_CONFIG, ...parsed };
|
|
} catch {
|
|
return DEFAULT_CONFIG;
|
|
}
|
|
}
|
|
|
|
/** 保存配置到 localStorage */
|
|
export function saveConfig(config: SessionConfig): void {
|
|
try {
|
|
localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
|
|
// 派发自定义事件,通知 App 更新 locale
|
|
window.dispatchEvent(new CustomEvent("camtalk-config-changed"));
|
|
} catch {
|
|
// localStorage 不可用时静默失败
|
|
}
|
|
}
|
|
|
|
/** 加载主题偏好 */
|
|
export function loadTheme(): Theme {
|
|
try {
|
|
const raw = localStorage.getItem(THEME_KEY);
|
|
if (raw === "light" || raw === "dark") return raw;
|
|
} catch { /* ignore */ }
|
|
return "dark";
|
|
}
|
|
|
|
/** 保存主题偏好 */
|
|
export function saveTheme(theme: Theme): void {
|
|
try {
|
|
localStorage.setItem(THEME_KEY, theme);
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
// ---- 会话历史存储 ----
|
|
|
|
const SESSIONS_KEY = "camtalk:sessions";
|
|
const SESSION_MSG_PREFIX = "camtalk:session:";
|
|
|
|
/** 加载所有会话摘要(按 lastActiveAt 倒序) */
|
|
export function loadSessionSummaries(): SessionSummary[] {
|
|
try {
|
|
const raw = localStorage.getItem(SESSIONS_KEY);
|
|
if (!raw) return [];
|
|
return (JSON.parse(raw) as SessionSummary[]).sort((a, b) => b.lastActiveAt - a.lastActiveAt);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/** 保存会话摘要列表 */
|
|
export function saveSessionSummaries(sessions: SessionSummary[]): void {
|
|
try {
|
|
localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions));
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 加载单个会话的消息历史 */
|
|
export function loadSessionMessages(sessionId: string): ChatMessage[] {
|
|
try {
|
|
const raw = localStorage.getItem(SESSION_MSG_PREFIX + sessionId);
|
|
if (!raw) return [];
|
|
return JSON.parse(raw) as ChatMessage[];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/** 保存单个会话的消息历史 */
|
|
export function saveSessionMessages(sessionId: string, messages: ChatMessage[]): void {
|
|
try {
|
|
localStorage.setItem(SESSION_MSG_PREFIX + sessionId, JSON.stringify(messages));
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 删除单个会话的消息历史 */
|
|
export function deleteSessionMessages(sessionId: string): void {
|
|
try {
|
|
localStorage.removeItem(SESSION_MSG_PREFIX + sessionId);
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
// ---- Auth Token 存储 ----
|
|
|
|
export interface StoredUser {
|
|
id: string;
|
|
username: string;
|
|
created_at: string;
|
|
}
|
|
|
|
/** 保存 access token */
|
|
export function saveAccessToken(token: string): void {
|
|
try {
|
|
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 获取 access token */
|
|
export function loadAccessToken(): string | null {
|
|
try {
|
|
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** 保存 refresh token */
|
|
export function saveRefreshToken(token: string): void {
|
|
try {
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 获取 refresh token */
|
|
export function loadRefreshToken(): string | null {
|
|
try {
|
|
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** 保存用户信息 */
|
|
export function saveUser(user: StoredUser): void {
|
|
try {
|
|
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
/** 获取用户信息 */
|
|
export function loadUser(): StoredUser | null {
|
|
try {
|
|
const raw = localStorage.getItem(USER_KEY);
|
|
if (!raw) return null;
|
|
return JSON.parse(raw) as StoredUser;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** 清除所有 auth 数据 */
|
|
export function clearAuth(): void {
|
|
try {
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
localStorage.removeItem(USER_KEY);
|
|
} catch { /* ignore */ }
|
|
}
|