diff --git a/package.json b/package.json index 1094739..c6253e2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ai-translate-client", "private": true, - "version": "0.3.3", + "version": "0.3.4", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e23fe69..7d8decc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "ai-translate-client" -version = "0.3.3" +version = "0.3.4" dependencies = [ "futures-util", "reqwest 0.12.28", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index aa80681..75fdb1c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ai-translate-client" -version = "0.3.3" +version = "0.3.4" description = "A client using AI models to translate" authors = ["Julian"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 94ef9f9..f029fc0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ai-translate-client", - "version": "0.3.3", + "version": "0.3.4", "identifier": "top.volan.ai-translate-client", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/App.vue b/src/App.vue index 69c1120..fc33cf4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,7 +17,9 @@ import { Type, Plus, Save, - Play + Play, + Clock, + Search } from 'lucide-vue-next'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; @@ -54,7 +56,45 @@ const toggleTheme = () => { settings.isDark = !settings.isDark; }; -const view = ref<'translate' | 'settings' | 'logs'>('translate'); +const view = ref<'translate' | 'settings' | 'logs' | 'history'>('translate'); + +// History Management +const searchQuery = ref(''); +const selectedHistoryId = ref(null); + +const filteredHistory = computed(() => { + if (!searchQuery.value.trim()) return settings.history; + const q = searchQuery.value.toLowerCase(); + return settings.history.filter(h => + h.sourceText.toLowerCase().includes(q) || + h.targetText.toLowerCase().includes(q) + ); +}); + +const selectedHistoryItem = computed(() => + settings.history.find(h => h.id === selectedHistoryId.value) || null +); + +watch(filteredHistory, (newVal) => { + if (newVal.length > 0 && !selectedHistoryId.value) { + selectedHistoryId.value = newVal[0].id; + } +}, { immediate: true }); + +const deleteHistoryItem = (id: string) => { + settings.history = settings.history.filter(h => h.id !== id); + if (selectedHistoryId.value === id) { + selectedHistoryId.value = filteredHistory.value[0]?.id || null; + } +}; + +const copyHistoryText = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch (err) { + console.error('Failed to copy history text: ', err); + } +}; // Profile Management const newProfileName = ref(''); @@ -423,6 +463,18 @@ const translate = async () => { targetText.value = response; } settings.addLog('response', 'Translation completed'); + + // Save to history + settings.addHistory({ + sourceLang: { ...sourceLang.value }, + targetLang: { ...targetLang.value }, + sourceText: sourceText.value, + targetText: settings.enableStreaming ? targetText.value : response, + context: context.value, + speakerIdentity: settings.speakerIdentity, + toneRegister: settings.toneRegister, + modelName: settings.modelName + }); } catch (err: any) { const errorMsg = String(err); settings.addLog('error', errorMsg); @@ -469,6 +521,13 @@ const translate = async () => { > + @@ -825,6 +884,161 @@ const translate = async () => { + +
+ +
+
+
+ + +
+
+ 共 {{ filteredHistory.length }} 条记录 + +
+
+ +
+
+ +

暂无相关历史

+
+
+
+
+ {{ item.sourceLang.code }} + + {{ item.targetLang.code }} +
+ {{ item.timestamp.split(' ')[1].substring(0, 5) }} +
+

{{ item.sourceText }}

+

{{ item.targetText }}

+ + +
+
+
+ + +
+ +
+
+ +
+

请从左侧选择一条历史记录查看详情

+
+
+
+
diff --git a/src/stores/settings.ts b/src/stores/settings.ts index 7ac814a..b6b6176 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -114,6 +114,19 @@ export interface ApiProfile { modelName: string; } +export interface HistoryItem { + id: string; + timestamp: string; + sourceLang: Language; + targetLang: Language; + sourceText: string; + targetText: string; + context: string; + speakerIdentity: string; + toneRegister: string; + modelName: string; +} + export const useSettingsStore = defineStore('settings', () => { const isDark = useLocalStorage('is-dark', false); const apiBaseUrl = useLocalStorage('api-base-url', 'http://localhost:11434/v1'); @@ -147,6 +160,7 @@ export const useSettingsStore = defineStore('settings', () => { }); const logs = ref<{ timestamp: string; type: 'request' | 'response' | 'error'; content: any }[]>([]); + const history = useLocalStorage('translation-history-v1', []); const addLog = (type: 'request' | 'response' | 'error', content: any) => { const now = new Date(); @@ -160,6 +174,22 @@ export const useSettingsStore = defineStore('settings', () => { if (logs.value.length > 20) logs.value.pop(); }; + const addHistory = (item: Omit) => { + const now = new Date(); + const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; + + history.value.unshift({ + ...item, + id: crypto.randomUUID(), + timestamp + }); + + // 限制 100 条 + if (history.value.length > 100) { + history.value = history.value.slice(0, 100); + } + }; + return { isDark, apiBaseUrl, @@ -177,6 +207,8 @@ export const useSettingsStore = defineStore('settings', () => { speakerIdentity, toneRegister, logs, - addLog + history, + addLog, + addHistory }; });