365 lines
11 KiB
TypeScript
365 lines
11 KiB
TypeScript
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 || '退出登录失败,请稍后重试';
|
|
}
|
|
}
|
|
}
|
|
});
|