diff --git a/src/App.vue b/src/App.vue index d262a86..e4b9df3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,7 +6,8 @@ import { FileText, Sun, Moon, - Clock + Clock, + MessageSquare } from 'lucide-vue-next'; import { useSettingsStore } from './stores/settings'; import pkg from '../package.json'; @@ -14,6 +15,7 @@ import { cn } from './lib/utils'; // Import newly separated views import TranslationView from './components/TranslationView.vue'; +import ConversationView from './components/ConversationView.vue'; import SettingsView from './components/SettingsView.vue'; import LogsView from './components/LogsView.vue'; import HistoryView from './components/HistoryView.vue'; @@ -34,7 +36,7 @@ const toggleTheme = () => { }; // Global Routing State -const view = ref<'translate' | 'settings' | 'logs' | 'history'>('translate'); +const view = ref<'translate' | 'conversation' | 'settings' | 'logs' | 'history'>('translate'); @@ -55,6 +57,13 @@ const view = ref<'translate' | 'settings' | 'logs' | 'history'>('translate'); + + + ('translate'); + diff --git a/src/components/ConversationView.vue b/src/components/ConversationView.vue new file mode 100644 index 0000000..a235b55 --- /dev/null +++ b/src/components/ConversationView.vue @@ -0,0 +1,558 @@ + + + + + + + + + 对话列表 + + + 新建会话 + + + + + + + + + + + + 暂无相关会话 + + + + + + {{ session.partner.name.charAt(0).toUpperCase() }} + + {{ session.partner.name }} + + {{ session.lastActivity.split(' ')[1].substring(0, 5) }} + + + {{ session.messages.length > 0 ? session.messages[session.messages.length - 1].translated : '开启新翻译对话...' }} + + + + + + + + + + + + + + + + + 我 + + {{ activeSession.partner.name.charAt(0).toUpperCase() }} + + + + {{ activeSession.partner.name }} + 正在与你说 {{ activeSession.partner.language.displayName }} 的同事对话 + + + + + + 上下文模式已开启 + + + + + + + + + 开始你的第一句翻译吧 + + + + + + {{ msg.sender === 'me' ? '我' : activeSession.partner.name }} + {{ msg.timestamp.split(' ')[1].substring(0, 5) }} + + + + + + + {{ msg.original }} + + + + + + {{ msg.translated }} + + + + + + + + + + + + + + + + + + + + + + + + {{ activeSession.partner.name }} 说 ({{ activeSession.partner.language.displayName }}) + + 自动识别语气 + + + + + + + + + + + + + + + + {{ activeSession.me.name }} 说 ({{ activeSession.me.language.displayName }}) + + + + + + + 语气: {{ TONE_REGISTER_OPTIONS.find(o => o.value === activeSession?.me.tone)?.label || '随和' }} + + + + + + + {{ opt.label }} + + + + + + + + + + + + + + + + + + + + + + + + + 选择或创建一个对话 + 在左侧侧边栏管理你的翻译会话 + + + + 开启新对话 + + + + + + + + + + + 创建新会话 + + + + + + + + + + 我的身份设定 + + + 姓名 + + + + 使用语言 + + {{ lang.displayName }} + + + + + + 性别 + + {{ opt.label }} + + + + + + + + 对方身份设定 + + + 姓名 + + + + 使用语言 + + {{ lang.displayName }} + + + + + + 性别 + + {{ opt.label }} + + + + + + + 开始会话 + + + + + + + + + + diff --git a/src/stores/settings.ts b/src/stores/settings.ts index ef9f91f..50716c3 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -127,6 +127,50 @@ export interface HistoryItem { modelName: string; } +export interface Participant { + name: string; + gender: string; + language: Language; + tone: string; +} + +export interface ChatMessage { + id: string; + sender: 'me' | 'partner'; + original: string; + translated: string; + timestamp: string; +} + +export interface ChatSession { + id: string; + title: string; + me: Participant; + partner: Participant; + messages: ChatMessage[]; + lastActivity: string; +} + +export const CONVERSATION_SYSTEM_PROMPT_TEMPLATE = `You are a professional real-time conversation translator. +Current Context: +- Role A (Me): {ME_NAME}, Gender: {ME_GENDER}, Language: {ME_LANG}. +- Role B (Partner): {PART_NAME}, Gender: {PART_GENDER}, Language: {PART_LANG}. + +[Conversation History] +{HISTORY_BLOCK} + +[Current Task] +Translate the incoming text from {FROM_LANG} to {TO_LANG}. + +[Constraints] +1. Contextual Awareness: Use the [Conversation History] to resolve pronouns (it, that, etc.) and maintain consistency. +2. Tone & Register: + - If translating for 'Me', strictly use the tone: {MY_TONE}. + - If translating for 'Partner', auto-detect and preserve their original tone/emotion. +3. Natural Flow: Keep the translation concise and natural for a chat environment. Avoid "translationese". +4. Strictly avoid over-translation: Do not add extra information not present in the source text. +5. Output ONLY the translated text, no explanations.`; + export const useSettingsStore = defineStore('settings', () => { const isDark = useLocalStorage('is-dark', false); const apiBaseUrl = useLocalStorage('api-base-url', 'http://localhost:11434/v1'); @@ -162,6 +206,10 @@ export const useSettingsStore = defineStore('settings', () => { const logs = ref<{ id: string; timestamp: string; type: 'request' | 'response' | 'error'; content: any; curl?: string }[]>([]); const history = useLocalStorage('translation-history-v1', []); + // 对话模式状态 + const chatSessions = useLocalStorage('chat-sessions-v1', []); + const activeSessionId = useLocalStorage('active-session-id-v1', null); + const addLog = (type: 'request' | 'response' | 'error', content: any, curl?: string) => { 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')}`; @@ -202,6 +250,72 @@ export const useSettingsStore = defineStore('settings', () => { } }; + // 对话模式方法 + const createSession = (me: Participant, partner: Participant) => { + const id = crypto.randomUUID(); + 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')}`; + + const newSession: ChatSession = { + id, + title: `${partner.name} 的对话`, + me, + partner, + messages: [], + lastActivity: timestamp + }; + + chatSessions.value.unshift(newSession); + activeSessionId.value = id; + return id; + }; + + const deleteSession = (id: string) => { + chatSessions.value = chatSessions.value.filter(s => s.id !== id); + if (activeSessionId.value === id) { + activeSessionId.value = chatSessions.value[0]?.id || null; + } + }; + + const addMessageToSession = (sessionId: string, sender: 'me' | 'partner', original: string, translated: string = '') => { + const session = chatSessions.value.find(s => s.id === sessionId); + if (session) { + 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')}`; + + const newMessage: ChatMessage = { + id: crypto.randomUUID(), + sender, + original, + translated, + timestamp + }; + + session.messages.push(newMessage); + session.lastActivity = timestamp; + + // 将活跃会话移至顶部 + const index = chatSessions.value.findIndex(s => s.id === sessionId); + if (index > 0) { + const [s] = chatSessions.value.splice(index, 1); + chatSessions.value.unshift(s); + } + + return newMessage.id; + } + return null; + }; + + const updateChatMessage = (sessionId: string, messageId: string, updates: Partial) => { + const session = chatSessions.value.find(s => s.id === sessionId); + if (session) { + const message = session.messages.find(m => m.id === messageId); + if (message) { + Object.assign(message, updates); + } + } + }; + return { isDark, apiBaseUrl, @@ -220,8 +334,14 @@ export const useSettingsStore = defineStore('settings', () => { toneRegister, logs, history, + chatSessions, + activeSessionId, addLog, addHistory, - updateHistoryItem + updateHistoryItem, + createSession, + deleteSession, + addMessageToSession, + updateChatMessage }; });
暂无相关会话
+ {{ session.messages.length > 0 ? session.messages[session.messages.length - 1].translated : '开启新翻译对话...' }} +
开始你的第一句翻译吧
+ {{ msg.original }} +
+ {{ msg.translated }} + +
在左侧侧边栏管理你的翻译会话