diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1aac7e4..7150961 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -18,7 +18,7 @@ import { loadPluginFrontendModules } from './plugins/runtime' import { naiveThemeOverrides } from './theme/naive' const router = useRouter() -const { locale, toggleLocale, tr } = useI18n() +const { locale, toggleLocale, tr, translateSeededText } = useI18n() const accountOptions = computed(() => [ { key: 'account', label: tr('账号设置', 'Account Settings') }, @@ -111,8 +111,8 @@ onBeforeUnmount(() => { diff --git a/frontend/src/components/FocusMode.vue b/frontend/src/components/FocusMode.vue index 2fc608d..e54d230 100644 --- a/frontend/src/components/FocusMode.vue +++ b/frontend/src/components/FocusMode.vue @@ -13,7 +13,7 @@ import OutcomeComposer from './OutcomeComposer.vue' import { useI18n } from '../i18n' import { Check, ChevronLeft, ChevronRight } from '@lucide/vue' -const { locale, tr, translateSeededText, translateNotesMarkdown } = useI18n() +const { locale, toggleLocale, tr, translateSeededText, translateNotesMarkdown } = useI18n() const props = defineProps<{ show: boolean @@ -96,6 +96,7 @@ watch(() => currentItem.value, (item) => { }, { immediate: true }) watch(locale, (nextLocale) => { + message.destroyAll() localNotes.value = translateNotesMarkdown(localNotes.value, nextLocale) }) @@ -174,6 +175,7 @@ async function persistCurrentNotes(): Promise { emit('reload') return true } catch (caught) { + message.destroyAll() message.error(errorMessage(caught, tr('议题笔记保存失败', 'Failed to save agenda notes'))) return false } finally { @@ -190,16 +192,18 @@ async function completeAndAdvance() { method: 'POST', body: JSON.stringify({ expected_version: localVersion.value }), }) + message.destroyAll() message.success( locale.value === 'en' - ? `Agenda "${currentItem.value.title}" completed` - : `议题「${currentItem.value.title}」已完成`, + ? `Agenda "${translateSeededText(currentItem.value.title, 'en')}" completed` + : `议题「${translateSeededText(currentItem.value.title, 'zh')}」已完成`, ) emit('reload') if (result.next_agenda_item_id) { selectedAgendaId.value = result.next_agenda_item_id } } catch (caught) { + message.destroyAll() message.error(errorMessage(caught, tr('完成议题失败', 'Failed to complete agenda item'))) } finally { advancing.value = false @@ -232,6 +236,7 @@ async function smartExtractFocusNotes() { const decisions = res.decisions ?? res.extracted?.decisions ?? 0 const actions = res.actions ?? res.extracted?.actions ?? 0 const questions = res.questions ?? res.extracted?.questions ?? 0 + message.destroyAll() message.success( locale.value === 'en' ? `Extracted ${decisions} decisions, ${actions} actions, and ${questions} open questions` @@ -239,6 +244,7 @@ async function smartExtractFocusNotes() { ) emit('reload') } catch (caught) { + message.destroyAll() message.error(errorMessage(caught, tr('AI 提炼失败,请检查笔记标签格式', 'AI extraction failed, please check notes format'))) } finally { extracting.value = false @@ -270,6 +276,7 @@ async function toggleActionStatus(action: ActionItem, targetStatus?: ActionStatu }) action.status = updated.status action.version = updated.version + message.destroyAll() message.success( locale.value === 'en' ? `Action status updated to: ${actionStatusLabel(nextStatus)}` @@ -277,6 +284,7 @@ async function toggleActionStatus(action: ActionItem, targetStatus?: ActionStatu ) emit('reload') } catch (caught) { + message.destroyAll() message.error(errorMessage(caught, tr('更新行动项失败', 'Failed to update action item'))) } } @@ -400,6 +408,16 @@ const noteActionTags = computed(() => { {{ saving ? tr('保存中…', 'Saving…') : tr('保存笔记', 'Save Notes') }} + + @@ -780,6 +798,28 @@ const noteActionTags = computed(() => { font-family: inherit; } +.focus-lang-btn { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 10px; + border-radius: 8px; + border: 1px solid #cbd5e1; + background: white; + color: #334155; + font-size: 0.8rem; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + flex-shrink: 0; +} + +.focus-lang-btn:hover { + background: #f1f5f9; + border-color: #94a3b8; +} + .focus-exit-btn { display: inline-flex; align-items: center; @@ -836,18 +876,24 @@ const noteActionTags = computed(() => { .agenda-title-row { display: flex; align-items: baseline; + justify-content: space-between; gap: 12px; margin-bottom: 6px; } .focus-agenda-title { margin: 0; - font-size: 1.65rem; + flex: 1; + min-width: 0; + overflow-wrap: anywhere; + font-size: 1.5rem; font-weight: 800; color: #0f172a; } .focus-agenda-type { + flex-shrink: 0; + white-space: nowrap; font-size: 0.78rem; color: #64748b; font-weight: 600; diff --git a/frontend/src/components/MarkdownEditor.vue b/frontend/src/components/MarkdownEditor.vue index 00d7cd1..3d0ac76 100644 --- a/frontend/src/components/MarkdownEditor.vue +++ b/frontend/src/components/MarkdownEditor.vue @@ -1,5 +1,7 @@