迁移和清理残留的data.json中的旧字段

This commit is contained in:
mali-i 2026-05-26 20:14:03 +08:00
parent c932d28d12
commit cc82389f09
4 changed files with 180 additions and 13 deletions

View file

@ -486,7 +486,7 @@ const submitPrompt = async (
const savedPrompt = await promptStore.addPrompt(
promptText,
fullResponse,
selectedModelConfig.id,
selectedModelConfig.modelId,
contextRefIds,
sourceConversationId,
sourceSelection

View file

@ -1,9 +1,25 @@
import { Plugin, WorkspaceLeaf, Notice, ObsidianProtocolData } from "obsidian";
import "./tailwind.css";
import { DeepSeekAIAssistant_SettingTab } from "./setting-tab";
import {SettingsInterfaceType, DEFAULT_SETTINGS} from './settings' // 导入设置接口类型
import {SettingsInterfaceType, DEFAULT_SETTINGS, type Conversation, type DataStructure, type ModelConfig} from './settings' // 导入设置接口类型
import { DeepSeekAIAssistant_ItemView } from "./my-itemview";
type LegacyMessage = {
timestamp?: number;
role?: string;
content?: string;
model?: string;
};
type LegacyConversation = {
id?: string;
title?: string;
createdAt?: number;
updatedAt?: number;
model?: string;
messages?: LegacyMessage[];
};
export default class Plugin_Deepseek_AI_Assistant extends Plugin {
// private vueApp: ReturnType<typeof createApp> | null = null; // 创建vue应用实例
@ -53,7 +69,13 @@ export default class Plugin_Deepseek_AI_Assistant extends Plugin {
}
async loadSettings(){
this.settings = Object.assign({},DEFAULT_SETTINGS,await this.loadData());
const loadedData = await this.loadData();
const { settings, shouldSave } = this.migrateSettings(loadedData);
this.settings = settings;
if (shouldSave) {
await this.saveData(this.settings);
}
}
async saveSettings(){
@ -80,4 +102,159 @@ export default class Plugin_Deepseek_AI_Assistant extends Plugin {
}
}
private migrateSettings(loadedData: any): { settings: SettingsInterfaceType; shouldSave: boolean } {
const loaded = loadedData || {};
const models = this.migrateModels(loaded);
const promptStats = this.migratePromptStats(loaded, models);
const settings: SettingsInterfaceType = { models, promptStats };
const allowedKeys = new Set(['models', 'promptStats']);
const hasDeprecatedFields = Object.keys(loaded).some((key) => !allowedKeys.has(key));
return {
settings,
shouldSave: hasDeprecatedFields || Boolean(loaded.promptStatus) || !loaded.promptStats,
};
}
private migrateModels(loaded: any): ModelConfig[] {
const loadedModels = Array.isArray(loaded.models) ? loaded.models : null;
const legacyCustomModels = Array.isArray(loaded.customModels) ? loaded.customModels : null;
const sourceModels = loadedModels?.length
? loadedModels
: legacyCustomModels?.length
? legacyCustomModels
: DEFAULT_SETTINGS.models;
const legacyApiKey = typeof loaded.API_KEY === 'string' ? loaded.API_KEY : '';
const legacyApiUrl = typeof loaded.API_URL === 'string' ? loaded.API_URL : '';
return sourceModels.map((model: any, index: number) => ({
id: String(model.id || model.uuid || model.name || model.modelId || model.model || `legacy-model-${index}`),
name: String(model.name || model.modelId || model.model || `Legacy Model ${index + 1}`),
modelId: String(model.modelId || model.model || model.id || DEFAULT_SETTINGS.models[0].modelId),
apiKey: model.apiKey || model.api_key || legacyApiKey,
apiUrl: model.apiUrl || model.api_url || model.baseURL || model.baseUrl || legacyApiUrl || DEFAULT_SETTINGS.models[0].apiUrl,
providerUrl: model.providerUrl || model.provider_url || '',
}));
}
private migratePromptStats(loaded: any, models: ModelConfig[]): DataStructure {
const promptStats = this.clonePromptStats(loaded.promptStats || loaded.promptStatus || {});
const existingIds = new Set<string>();
const existingContentKeys = new Set<string>();
Object.values(promptStats).forEach((dayStats: any) => {
const promptContent = Array.isArray(dayStats?.prompt_content) ? dayStats.prompt_content : [];
promptContent.forEach((item: Conversation) => {
if (item.id_timestamp) {
existingIds.add(item.id_timestamp);
}
existingContentKeys.add(this.buildConversationContentKey(item.prompt, item.answer));
});
});
if (!Array.isArray(loaded.conversations)) {
return promptStats;
}
loaded.conversations.forEach((legacyConversation: LegacyConversation) => {
const messages = Array.isArray(legacyConversation.messages) ? legacyConversation.messages : [];
messages.forEach((message, index) => {
if (message.role !== 'user' || !message.content) {
return;
}
const assistantMessage = this.findNextAssistantMessage(messages, index + 1);
const prompt = message.content;
const answer = assistantMessage?.content || '';
const contentKey = this.buildConversationContentKey(prompt, answer);
if (existingContentKeys.has(contentKey)) {
return;
}
const timestamp = this.normalizeTimestamp(
assistantMessage?.timestamp || message.timestamp || legacyConversation.updatedAt || legacyConversation.createdAt
);
const idTimestamp = this.createUniqueTimestampId(timestamp, existingIds);
const dateKey = new Date(timestamp).toISOString().split('T')[0];
if (!promptStats[dateKey]) {
promptStats[dateKey] = { num: 0, prompt_content: [] };
}
promptStats[dateKey].prompt_content.push({
id_timestamp: idTimestamp,
prompt,
answer,
model: this.resolveModelValue(message.model || legacyConversation.model, models),
});
promptStats[dateKey].num = promptStats[dateKey].prompt_content.length;
existingContentKeys.add(contentKey);
});
});
return promptStats;
}
private clonePromptStats(promptStats: any): DataStructure {
const clonedStats: DataStructure = {};
Object.keys(promptStats || {}).forEach((date) => {
const dayStats = promptStats[date];
const promptContent = Array.isArray(dayStats?.prompt_content) ? dayStats.prompt_content : [];
clonedStats[date] = {
num: promptContent.length,
prompt_content: promptContent.map((item: Conversation) => ({ ...item })),
};
});
return clonedStats;
}
private findNextAssistantMessage(messages: LegacyMessage[], startIndex: number): LegacyMessage | null {
for (let index = startIndex; index < messages.length; index += 1) {
const message = messages[index];
if (message.role === 'user') {
return null;
}
if (message.role === 'assistant') {
return message;
}
}
return null;
}
private normalizeTimestamp(timestamp?: number): number {
if (typeof timestamp === 'number' && Number.isFinite(timestamp)) {
return timestamp > 100000000000 ? timestamp : timestamp * 1000;
}
return Date.now();
}
private createUniqueTimestampId(timestamp: number, existingIds: Set<string>): string {
let candidate = timestamp;
while (existingIds.has(String(candidate))) {
candidate += 1;
}
const id = String(candidate);
existingIds.add(id);
return id;
}
private buildConversationContentKey(prompt = '', answer = ''): string {
return `${prompt.replace(/\s+/g, ' ').trim()}\n---\n${answer.replace(/\s+/g, ' ').trim()}`;
}
private resolveModelValue(model: string | undefined, models: ModelConfig[]): string | undefined {
if (!model) {
return undefined;
}
const found = models.find((item) => item.id === model || item.modelId === model || item.name === model);
return found?.modelId || model;
}
}

View file

@ -17,11 +17,6 @@ export class DeepSeekAIAssistant_SettingTab extends PluginSettingTab {
const models = this.plugin.settings.models || [];
// Check migration (simple check)
if (!this.plugin.settings.models && (this.plugin.settings as any).customModels) {
// Basic migration logic could go here, but for now we rely on user adding new ones or using defaults
}
// --- Models List ---
models.forEach((model, index) => {
const setting = new Setting(containerEl)

View file

@ -30,11 +30,6 @@ export interface ModelConfig {
}
export interface SettingsInterfaceType{
// 废弃旧的顶层字段,为了类型兼容暂时保留或改为可选,但核心逻辑使用 models 列表
API_KEY?:string; // Deprecated
API_URL?:string; // Deprecated
customModels?: any[]; // Deprecated
models: ModelConfig[]; // 新的主要配置项
promptStats: DataStructure;
}