From cc82389f09e8e42d66ae625dcad7c7c82a9fb35f Mon Sep 17 00:00:00 2001 From: mali-i Date: Tue, 26 May 2026 20:14:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=92=8C=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=AE=8B=E7=95=99=E7=9A=84data.json=E4=B8=AD=E7=9A=84=E6=97=A7?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AICard.vue | 2 +- src/main.ts | 181 +++++++++++++++++++++++++++++++++++++- src/setting-tab.ts | 5 -- src/settings.ts | 5 -- 4 files changed, 180 insertions(+), 13 deletions(-) diff --git a/src/components/AICard.vue b/src/components/AICard.vue index 14d1e75..90d6005 100644 --- a/src/components/AICard.vue +++ b/src/components/AICard.vue @@ -486,7 +486,7 @@ const submitPrompt = async ( const savedPrompt = await promptStore.addPrompt( promptText, fullResponse, - selectedModelConfig.id, + selectedModelConfig.modelId, contextRefIds, sourceConversationId, sourceSelection diff --git a/src/main.ts b/src/main.ts index c9f19a6..80f071f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 | 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(); + const existingContentKeys = new Set(); + + 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 { + 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; + } } diff --git a/src/setting-tab.ts b/src/setting-tab.ts index f552049..0e60c25 100644 --- a/src/setting-tab.ts +++ b/src/setting-tab.ts @@ -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) diff --git a/src/settings.ts b/src/settings.ts index 3d4da97..d70c94e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -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; }