import { defineStore } from 'pinia'; interface PersonalForm { enabled: boolean; self_identify: string; user_name: string; profession: string; tone: string; considerations: string[]; thinking_interval: number | null; disabled_tool_categories: string[]; } interface PersonalizationState { visible: boolean; loading: boolean; saving: boolean; loaded: boolean; status: string; error: string; maxConsiderations: number; toggleUpdating: boolean; overlayPressActive: boolean; newConsideration: string; tonePresets: string[]; draggedConsiderationIndex: number | null; form: PersonalForm; toolCategories: Array<{ id: string; label: string }>; thinkingIntervalDefault: number; thinkingIntervalRange: { min: number; max: number }; } const DEFAULT_INTERVAL = 10; const DEFAULT_INTERVAL_RANGE = { min: 1, max: 50 }; const defaultForm = (): PersonalForm => ({ enabled: false, self_identify: '', user_name: '', profession: '', tone: '', considerations: [], thinking_interval: null, disabled_tool_categories: [] }); export const usePersonalizationStore = defineStore('personalization', { state: (): PersonalizationState => ({ visible: false, loading: false, saving: false, loaded: false, status: '', error: '', maxConsiderations: 10, toggleUpdating: false, overlayPressActive: false, newConsideration: '', tonePresets: ['健谈', '幽默', '直言不讳', '鼓励性', '诗意', '企业商务', '打破常规', '同理心'], draggedConsiderationIndex: null, form: defaultForm(), toolCategories: [], thinkingIntervalDefault: DEFAULT_INTERVAL, thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE } }), actions: { async openDrawer() { this.visible = true; if (!this.loaded && !this.loading) { await this.fetchPersonalization(); } }, closeDrawer() { this.visible = false; this.draggedConsiderationIndex = null; this.overlayPressActive = false; }, handleOverlayPressStart(event: Event) { if (event && (event as MouseEvent).type === 'mousedown') { const mouse = event as MouseEvent; if (mouse.button !== 0) { return; } } this.overlayPressActive = true; }, handleOverlayPressEnd() { if (!this.overlayPressActive) { return; } this.overlayPressActive = false; this.closeDrawer(); }, handleOverlayPressCancel() { this.overlayPressActive = false; }, async fetchPersonalization() { this.loading = true; this.error = ''; try { const resp = await fetch('/api/personalization'); const result = await resp.json(); if (!resp.ok || !result.success) { throw new Error(result.error || '加载失败'); } this.applyPersonalizationData(result.data || {}); this.applyPersonalizationMeta(result); this.loaded = true; } catch (error: any) { this.error = error?.message || '加载失败'; } finally { this.loading = false; } }, applyPersonalizationData(data: any) { this.form = { enabled: !!data.enabled, self_identify: data.self_identify || '', user_name: data.user_name || '', profession: data.profession || '', tone: data.tone || '', considerations: Array.isArray(data.considerations) ? [...data.considerations] : [], thinking_interval: typeof data.thinking_interval === 'number' ? data.thinking_interval : null, disabled_tool_categories: Array.isArray(data.disabled_tool_categories) ? data.disabled_tool_categories.filter((item: unknown) => typeof item === 'string') : [] }; this.clearFeedback(); }, applyPersonalizationMeta(payload: any) { if (payload && typeof payload.thinking_interval_default === 'number') { this.thinkingIntervalDefault = payload.thinking_interval_default; } else { this.thinkingIntervalDefault = DEFAULT_INTERVAL; } if (payload && payload.thinking_interval_range) { const { min, max } = payload.thinking_interval_range; this.thinkingIntervalRange = { min: typeof min === 'number' ? min : DEFAULT_INTERVAL_RANGE.min, max: typeof max === 'number' ? max : DEFAULT_INTERVAL_RANGE.max }; } else { this.thinkingIntervalRange = { ...DEFAULT_INTERVAL_RANGE }; } if (payload && Array.isArray(payload.tool_categories)) { this.toolCategories = payload.tool_categories .map((item: { id?: string; label?: string } = {}) => ({ id: typeof item.id === 'string' ? item.id : String(item.id ?? ''), label: (item.label && String(item.label)) || (typeof item.id === 'string' ? item.id : String(item.id ?? '')) })) .filter((item: { id: string }) => !!item.id); } else { this.toolCategories = []; } }, clearFeedback() { this.status = ''; this.error = ''; }, async toggleEnabled() { if (this.toggleUpdating) { return; } const newValue = !this.form.enabled; const previousValue = this.form.enabled; this.toggleUpdating = true; this.status = ''; this.error = ''; this.form.enabled = newValue; try { const resp = await fetch('/api/personalization', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: newValue }) }); const result = await resp.json(); if (!resp.ok || !result.success) { throw new Error(result.error || '更新失败'); } if (result.data) { this.applyPersonalizationData(result.data); } this.applyPersonalizationMeta(result); const statusLabel = newValue ? '已启用' : '已停用'; this.status = statusLabel; setTimeout(() => { if (this.status === statusLabel) { this.status = ''; } }, 2000); } catch (error: any) { this.form.enabled = previousValue; this.error = error?.message || '更新失败'; } finally { this.toggleUpdating = false; } }, async save() { if (this.saving) { return; } this.saving = true; this.status = ''; this.error = ''; try { const resp = await fetch('/api/personalization', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.form) }); const result = await resp.json(); if (!resp.ok || !result.success) { throw new Error(result.error || '保存失败'); } this.applyPersonalizationData(result.data || {}); this.applyPersonalizationMeta(result); this.status = '已保存'; setTimeout(() => { if (this.status === '已保存') { this.status = ''; } }, 3000); } catch (error: any) { this.error = error?.message || '保存失败'; } finally { this.saving = false; } }, updateField(payload: { key: keyof PersonalForm; value: string }) { if (!payload || !payload.key) { return; } this.form = { ...this.form, [payload.key]: payload.value }; this.clearFeedback(); }, setThinkingInterval(value: number | null) { let target: number | null = value; if (typeof target === 'number') { if (Number.isNaN(target)) { target = null; } else { const rounded = Math.round(target); const min = this.thinkingIntervalRange.min ?? DEFAULT_INTERVAL_RANGE.min; const max = this.thinkingIntervalRange.max ?? DEFAULT_INTERVAL_RANGE.max; target = Math.max(min, Math.min(max, rounded)); if (target === this.thinkingIntervalDefault) { target = null; } } } this.form = { ...this.form, thinking_interval: target }; this.clearFeedback(); }, toggleDefaultToolCategory(categoryId: string) { if (!categoryId) { return; } const current = new Set(this.form.disabled_tool_categories || []); if (current.has(categoryId)) { current.delete(categoryId); } else { current.add(categoryId); } this.form = { ...this.form, disabled_tool_categories: Array.from(current) }; this.clearFeedback(); }, applyTonePreset(preset: string) { if (!preset) { return; } this.form = { ...this.form, tone: preset }; this.clearFeedback(); }, updateNewConsideration(value: string) { this.newConsideration = value; this.clearFeedback(); }, addConsideration() { if (!this.newConsideration) { return; } if (this.form.considerations.length >= this.maxConsiderations) { return; } this.form = { ...this.form, considerations: [...this.form.considerations, this.newConsideration] }; this.newConsideration = ''; this.clearFeedback(); }, removeConsideration(index: number) { const items = [...this.form.considerations]; items.splice(index, 1); this.form = { ...this.form, considerations: items }; this.clearFeedback(); }, considerationDragStart(index: number, event: DragEvent) { this.draggedConsiderationIndex = index; if (event && event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; } }, considerationDragOver(index: number, event: DragEvent) { if (event) { event.preventDefault(); } if (this.draggedConsiderationIndex === null || this.draggedConsiderationIndex === index) { return; } const items = [...this.form.considerations]; const [moved] = items.splice(this.draggedConsiderationIndex, 1); items.splice(index, 0, moved); this.form = { ...this.form, considerations: items }; this.draggedConsiderationIndex = index; this.clearFeedback(); }, considerationDrop(index: number, event: DragEvent) { if (event) { event.preventDefault(); } this.considerationDragEnd(); this.considerationDragOver(index, event); }, considerationDragEnd() { this.draggedConsiderationIndex = null; }, async logout() { try { const resp = await fetch('/logout', { method: 'POST' }); let result: any = {}; try { result = await resp.json(); } catch (err) { result = {}; } if (!resp.ok || (result && result.success === false)) { const message = (result && (result.error || result.message)) || '退出失败'; throw new Error(message); } window.location.href = '/login'; } catch (error: any) { console.error('退出登录失败:', error); this.error = error?.message || '退出登录失败,请稍后重试'; } } } });