system commit
This commit is contained in:
50
frontend/src/stores/authStore.ts
Normal file
50
frontend/src/stores/authStore.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
|
||||
interface UserInfo { id: string; email: string; }
|
||||
|
||||
interface AuthState {
|
||||
token: string | null;
|
||||
user: UserInfo | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: (): AuthState => ({
|
||||
token: localStorage.getItem('token'),
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
getters: {
|
||||
isAuthed: (s) => !!s.token,
|
||||
},
|
||||
actions: {
|
||||
async login(email: string, password: string) {
|
||||
this.loading = true; this.error = null;
|
||||
try {
|
||||
const { data } = await axios.post('/api/auth/login', { email, password });
|
||||
this.token = data.token; localStorage.setItem('token', data.token);
|
||||
this.user = data.user ? { id: data.user.id, email: data.user.email } : { id: 'me', email };
|
||||
} catch (e:any) {
|
||||
this.error = e.response?.data?.error || e.message;
|
||||
throw e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async register(email: string, password: string) {
|
||||
this.loading = true; this.error = null;
|
||||
try {
|
||||
const { data } = await axios.post('/api/auth/register', { email, password });
|
||||
this.token = data.token; localStorage.setItem('token', data.token);
|
||||
this.user = data.user ? { id: data.user.id, email: data.user.email } : { id: 'me', email };
|
||||
} catch (e:any) {
|
||||
this.error = e.response?.data?.error || e.message;
|
||||
throw e;
|
||||
} finally { this.loading = false; }
|
||||
},
|
||||
logout() { this.token = null; this.user = null; localStorage.removeItem('token'); }
|
||||
}
|
||||
});
|
66
frontend/src/stores/conversationStore.ts
Normal file
66
frontend/src/stores/conversationStore.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from './authStore';
|
||||
|
||||
export interface ConversationSummary { _id: string; title: string; modelId: string; updatedAt: string; }
|
||||
export interface ConversationMessage { role: 'user' | 'assistant' | 'system'; content: string; transient?: boolean }
|
||||
|
||||
interface ConversationState {
|
||||
list: ConversationSummary[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
currentId: string | null;
|
||||
messages: ConversationMessage[];
|
||||
}
|
||||
|
||||
export const useConversationStore = defineStore('conversation', {
|
||||
state: (): ConversationState => ({ list: [], loading: false, error: null, currentId: null, messages: [] }),
|
||||
actions: {
|
||||
async fetchList() {
|
||||
const auth = useAuthStore();
|
||||
if (!auth.token) { this.list = []; return; }
|
||||
this.loading = true; this.error = null;
|
||||
try {
|
||||
const { data } = await axios.get('/api/conversations', { headers: { Authorization: 'Bearer ' + auth.token } });
|
||||
this.list = data;
|
||||
} catch (e:any) { this.error = e.response?.data?.error || e.message; }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
async loadConversation(id: string) {
|
||||
const auth = useAuthStore();
|
||||
if (!auth.token) return;
|
||||
this.loading = true; this.error = null;
|
||||
try {
|
||||
const { data } = await axios.get('/api/conversations/' + id, { headers: { Authorization: 'Bearer ' + auth.token } });
|
||||
this.currentId = id;
|
||||
this.messages = data.messages || [];
|
||||
} catch (e:any) { this.error = e.response?.data?.error || e.message; }
|
||||
finally { this.loading = false; }
|
||||
},
|
||||
resetCurrent() { this.currentId = null; this.messages = []; },
|
||||
createNewConversation(notify?: string) {
|
||||
this.currentId = null;
|
||||
this.messages = [];
|
||||
if (notify) {
|
||||
this.messages.push({ role: 'system', content: notify, transient: true });
|
||||
}
|
||||
},
|
||||
pushMessage(m: ConversationMessage) { this.messages.push(m); },
|
||||
patchLastAssistant(content: string) {
|
||||
for (let i = this.messages.length - 1; i >=0; i--) {
|
||||
const msg = this.messages[i];
|
||||
if (msg.role === 'assistant') { msg.content = content; return; }
|
||||
}
|
||||
}
|
||||
,
|
||||
async deleteConversation(id: string) {
|
||||
const auth = useAuthStore();
|
||||
if (!auth.token) return;
|
||||
try {
|
||||
await axios.delete('/api/conversations/' + id, { headers: { Authorization: 'Bearer ' + auth.token } });
|
||||
this.list = this.list.filter(l => l._id !== id);
|
||||
if (this.currentId === id) this.resetCurrent();
|
||||
} catch (e:any) { this.error = e.response?.data?.error || e.message; }
|
||||
}
|
||||
}
|
||||
});
|
44
frontend/src/stores/modelStore.ts
Normal file
44
frontend/src/stores/modelStore.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export interface ModelInfo {
|
||||
id: string;
|
||||
label: string;
|
||||
provider: 'openai' | 'azureOpenAI' | 'anthropic' | 'google' | 'ollama' | 'unknown';
|
||||
model: string; // provider 实际模型名
|
||||
maxTokens?: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
currentModel: string;
|
||||
supportedModels: ModelInfo[];
|
||||
}
|
||||
|
||||
export const useModelStore = defineStore('modelStore', {
|
||||
state: (): State => ({
|
||||
// 从 localStorage 读取上次选择的模型,避免 SSR/Node 环境报错时使用短路
|
||||
currentModel: typeof window !== 'undefined' && localStorage.getItem('currentModel') ? String(localStorage.getItem('currentModel')) : 'gpt-4o-mini',
|
||||
supportedModels: [
|
||||
{ id: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'openai', model: 'gpt-4o-mini' },
|
||||
{ id: 'gpt-4o', label: 'GPT-4o', provider: 'openai', model: 'gpt-4o' },
|
||||
{ id: 'claude-3-5', label: 'Claude 3.5', provider: 'anthropic', model: 'claude-3-5-sonnet-latest' },
|
||||
{ id: 'gemini-flash', label: 'Gemini Flash', provider: 'google', model: 'gemini-1.5-flash' },
|
||||
{ id: 'ollama-llama3', label: 'Llama3 (本地)', provider: 'ollama', model: 'llama3' },
|
||||
{ id: 'deepseek-chat', label: 'DeepSeek Chat', provider: 'openai', model: 'deepseek-chat' }
|
||||
],
|
||||
}),
|
||||
actions: {
|
||||
setCurrentModel(id: string) {
|
||||
(this as any).currentModel = id;
|
||||
try { localStorage.setItem('currentModel', id); } catch (e) { /* ignore */ }
|
||||
},
|
||||
addModel(m: ModelInfo) {
|
||||
const self = this as any as State;
|
||||
if (!self.supportedModels.find((x: ModelInfo) => x.id === m.id)) {
|
||||
self.supportedModels.push(m);
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
currentModelInfo: (s: State) => s.supportedModels.find((m: ModelInfo) => m.id === s.currentModel),
|
||||
}
|
||||
});
|
21
frontend/src/stores/themeStore.ts
Normal file
21
frontend/src/stores/themeStore.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export type Theme = 'dark' | 'light';
|
||||
const THEME_KEY = 'appTheme';
|
||||
|
||||
export const useThemeStore = defineStore('themeStore', {
|
||||
state: () => ({
|
||||
theme: (localStorage.getItem(THEME_KEY) as Theme) || 'dark'
|
||||
}),
|
||||
actions: {
|
||||
applyTheme() {
|
||||
document.documentElement.setAttribute('data-theme', this.theme);
|
||||
},
|
||||
setTheme(t: Theme) {
|
||||
this.theme = t;
|
||||
localStorage.setItem(THEME_KEY, t);
|
||||
this.applyTheme();
|
||||
},
|
||||
toggle() { this.setTheme(this.theme === 'dark' ? 'light' : 'dark'); }
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user