diff --git a/apps/web/src/components/chat/chat-step/index.vue b/apps/web/src/components/chat/chat-step/index.vue index e090263c0..0375d3422 100644 --- a/apps/web/src/components/chat/chat-step/index.vue +++ b/apps/web/src/components/chat/chat-step/index.vue @@ -2,7 +2,7 @@
- + 推理中
- \ No newline at end of file + diff --git a/apps/web/src/components/context-window-badge/index.vue b/apps/web/src/components/context-window-badge/index.vue index b3c948864..be8d1e0e0 100644 --- a/apps/web/src/components/context-window-badge/index.vue +++ b/apps/web/src/components/context-window-badge/index.vue @@ -25,10 +25,10 @@ const formatted = computed(() => { const badgeClass = computed(() => { const ctx = props.contextWindow ?? 0 - if (ctx >= 1_000_000) return 'bg-violet-50 text-violet-700 dark:bg-violet-950 dark:text-violet-300' - if (ctx >= 100_000) return 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300' - if (ctx >= 32_000) return 'bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300' - if (ctx >= 8_000) return 'bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300' - return 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400' + if (ctx >= 1_000_000) return 'bg-context-window-xl text-context-window-foreground' + if (ctx >= 100_000) return 'bg-context-window-lg text-context-window-foreground' + if (ctx >= 32_000) return 'bg-context-window-md text-context-window-foreground' + if (ctx >= 8_000) return 'bg-context-window-sm text-context-window-foreground' + return 'bg-context-window-xs text-muted-foreground' }) diff --git a/apps/web/src/components/file-manager/file-list.vue b/apps/web/src/components/file-manager/file-list.vue index 8aa707585..58b4a6e2f 100644 --- a/apps/web/src/components/file-manager/file-list.vue +++ b/apps/web/src/components/file-manager/file-list.vue @@ -95,7 +95,7 @@ function handleClick(entry: HandlersFsFileInfo) {
{{ entry.name }} diff --git a/apps/web/src/components/file-manager/file-viewer.vue b/apps/web/src/components/file-manager/file-viewer.vue index 3dfb9f3c8..91d06eb51 100644 --- a/apps/web/src/components/file-manager/file-viewer.vue +++ b/apps/web/src/components/file-manager/file-viewer.vue @@ -165,7 +165,7 @@ onBeforeUnmount(() => { v-if="isText" type="button" size="sm" - class="absolute top-2 right-2 z-10 gap-1.5 bg-[#8B56E3] text-white shadow-md hover:bg-[#7c47d6] disabled:bg-[#8B56E3]/40 disabled:text-white/80" + class="absolute top-2 right-2 z-10 gap-1.5 bg-primary text-primary-foreground shadow-md hover:bg-brand-hover disabled:bg-primary/40 disabled:text-primary-foreground/80" :disabled="!isDirty || saving" :title="t('bots.files.save')" @click="handleSave" diff --git a/apps/web/src/components/model-capabilities/index.vue b/apps/web/src/components/model-capabilities/index.vue index c946e17b9..819f98212 100644 --- a/apps/web/src/components/model-capabilities/index.vue +++ b/apps/web/src/components/model-capabilities/index.vue @@ -29,10 +29,10 @@ const ICONS: Record = { } const CLASSES: Record = { - 'tool-call': 'bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300', - 'vision': 'bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300', - 'image-output': 'bg-pink-50 text-pink-700 dark:bg-pink-950 dark:text-pink-300', - 'reasoning': 'bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300', + 'tool-call': 'bg-capability-tool-soft text-capability-tool-foreground', + 'vision': 'bg-capability-vision-soft text-capability-vision-foreground', + 'image-output': 'bg-capability-image-soft text-capability-image-foreground', + 'reasoning': 'bg-capability-reasoning-soft text-capability-reasoning-foreground', } function iconOf(cap: string): Component { diff --git a/apps/web/src/components/settings-sidebar/index.vue b/apps/web/src/components/settings-sidebar/index.vue index 15cdc7272..30a54c410 100644 --- a/apps/web/src/components/settings-sidebar/index.vue +++ b/apps/web/src/components/settings-sidebar/index.vue @@ -38,7 +38,7 @@ :tooltip="item.title" :is-active="isItemActive(item.name)" :aria-current="isItemActive(item.name) ? 'page' : undefined" - class="h-9 gap-2 relative before:absolute before:w-0.5 before:top-1.5 before:bottom-1.5 before:left-0 before:rounded-full data-[active=true]:before:bg-[#8B56E3] group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0" + class="h-9 gap-2 relative before:absolute before:w-0.5 before:top-1.5 before:bottom-1.5 before:left-0 before:rounded-full data-[active=true]:before:bg-sidebar-primary group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:px-0" @click="router.push({ name: item.name })" > { switch (props.status) { - case 'success': return 'bg-green-500' - case 'error': return 'bg-red-500' - case 'warning': return 'bg-yellow-500' + case 'success': return 'bg-success' + case 'error': return 'bg-destructive' + case 'warning': return 'bg-warning' default: return 'bg-muted-foreground' } }) diff --git a/apps/web/src/constants/color-schemes.ts b/apps/web/src/constants/color-schemes.ts new file mode 100644 index 000000000..94dcb1866 --- /dev/null +++ b/apps/web/src/constants/color-schemes.ts @@ -0,0 +1,47 @@ +export const colorSchemeIds = ['memoh', 'ocean', 'forest', 'rose', 'amber'] as const + +export type ColorSchemeId = typeof colorSchemeIds[number] + +export interface ColorSchemeOption { + id: ColorSchemeId + labelKey: string + descriptionKey: string + swatches: string[] +} + +export const colorSchemes: ColorSchemeOption[] = [ + { + id: 'memoh', + labelKey: 'settings.appearance.colorSchemes.memoh', + descriptionKey: 'settings.appearance.colorSchemeDescriptions.memoh', + swatches: ['oklch(0.22 0.006 286)', 'oklch(0.985 0.001 286)', 'oklch(0.967 0.001 286.375)', 'oklch(0.92 0.004 286.32)', 'oklch(0.55 0.22 290)', 'oklch(0.62 0.16 150)', 'oklch(0.72 0.15 75)', 'oklch(0.60 0.19 355)'], + }, + { + id: 'ocean', + labelKey: 'settings.appearance.colorSchemes.ocean', + descriptionKey: 'settings.appearance.colorSchemeDescriptions.ocean', + swatches: ['oklch(0.22 0.006 286)', 'oklch(0.985 0.001 286)', 'oklch(0.967 0.001 286.375)', 'oklch(0.92 0.004 286.32)', 'oklch(0.56 0.15 230)', 'oklch(0.62 0.14 170)', 'oklch(0.72 0.14 80)', 'oklch(0.60 0.17 345)'], + }, + { + id: 'forest', + labelKey: 'settings.appearance.colorSchemes.forest', + descriptionKey: 'settings.appearance.colorSchemeDescriptions.forest', + swatches: ['oklch(0.22 0.006 286)', 'oklch(0.985 0.001 286)', 'oklch(0.967 0.001 286.375)', 'oklch(0.92 0.004 286.32)', 'oklch(0.50 0.14 150)', 'oklch(0.58 0.15 145)', 'oklch(0.72 0.14 80)', 'oklch(0.58 0.16 20)'], + }, + { + id: 'rose', + labelKey: 'settings.appearance.colorSchemes.rose', + descriptionKey: 'settings.appearance.colorSchemeDescriptions.rose', + swatches: ['oklch(0.22 0.006 286)', 'oklch(0.985 0.001 286)', 'oklch(0.967 0.001 286.375)', 'oklch(0.92 0.004 286.32)', 'oklch(0.58 0.18 355)', 'oklch(0.62 0.14 155)', 'oklch(0.72 0.14 75)', 'oklch(0.58 0.14 250)'], + }, + { + id: 'amber', + labelKey: 'settings.appearance.colorSchemes.amber', + descriptionKey: 'settings.appearance.colorSchemeDescriptions.amber', + swatches: ['oklch(0.22 0.006 286)', 'oklch(0.985 0.001 286)', 'oklch(0.967 0.001 286.375)', 'oklch(0.92 0.004 286.32)', 'oklch(0.62 0.15 70)', 'oklch(0.58 0.14 145)', 'oklch(0.70 0.15 75)', 'oklch(0.56 0.14 230)'], + }, +] + +export function isColorSchemeId(value: string): value is ColorSchemeId { + return colorSchemeIds.includes(value as ColorSchemeId) +} diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json index a7a6a9b03..38b2e7622 100644 --- a/apps/web/src/i18n/locales/en.json +++ b/apps/web/src/i18n/locales/en.json @@ -74,6 +74,7 @@ "mcp": "MCP", "platform": "Platform", "usage": "Usage", + "appearance": "Appearance", "supermarket": "Supermarket", "about": "About" }, @@ -114,6 +115,28 @@ "themePlaceholder": "Select theme", "themeLight": "Light", "themeDark": "Dark", + "appearance": { + "title": "Appearance", + "description": "Adjust language, theme, and the semantic color palette used across Memoh.", + "interface": "Interface", + "interfaceDescription": "These preferences apply immediately and are stored on this device.", + "colorScheme": "Color scheme", + "colorSchemeDescription": "Choose a palette for base, brand, status, event, chart, terminal, and diff colors.", + "colorSchemes": { + "memoh": "Memoh", + "ocean": "Ocean", + "forest": "Forest", + "rose": "Rose", + "amber": "Amber" + }, + "colorSchemeDescriptions": { + "memoh": "The default restrained purple palette.", + "ocean": "Cool blue with clear info states.", + "forest": "Green-focused and quiet.", + "rose": "Warm rose accents with soft contrast.", + "amber": "Golden highlights with practical status colors." + } + }, "langZh": "中文", "langEn": "English", "version": "Version", diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json index a5020f9e0..e2183bee3 100644 --- a/apps/web/src/i18n/locales/zh.json +++ b/apps/web/src/i18n/locales/zh.json @@ -74,6 +74,7 @@ "mcp": "MCP", "platform": "接入平台", "usage": "用量统计", + "appearance": "外观", "supermarket": "市场", "about": "关于" }, @@ -110,6 +111,28 @@ "themePlaceholder": "选择主题", "themeLight": "浅色", "themeDark": "深色", + "appearance": { + "title": "外观", + "description": "调整语言、主题,以及 Memoh 全局使用的语义化配色。", + "interface": "界面", + "interfaceDescription": "这些偏好会立即生效,并保存在当前设备上。", + "colorScheme": "配色方案", + "colorSchemeDescription": "选择基础、品牌、状态、事件、图表、终端和 diff 颜色使用的调色盘。", + "colorSchemes": { + "memoh": "Memoh", + "ocean": "海洋", + "forest": "森林", + "rose": "玫瑰", + "amber": "琥珀" + }, + "colorSchemeDescriptions": { + "memoh": "默认的克制紫色方案。", + "ocean": "冷静蓝色,信息状态更清晰。", + "forest": "以绿色为核心,更安静稳重。", + "rose": "温暖玫瑰色,保持柔和对比。", + "amber": "金色强调,搭配实用状态色。" + } + }, "langZh": "中文", "langEn": "English", "version": "版本", diff --git a/apps/web/src/pages/about/index.vue b/apps/web/src/pages/about/index.vue index c6e509b3d..e7af62e14 100644 --- a/apps/web/src/pages/about/index.vue +++ b/apps/web/src/pages/about/index.vue @@ -51,7 +51,7 @@ v-if="checkResult.isUpToDate" class="flex items-center gap-2 text-xs text-muted-foreground" > - + {{ $t('about.upToDate') }}
@@ -59,7 +59,7 @@
- + {{ $t('about.newVersionAvailable', { version: checkResult.latestVersion }) }}
@@ -85,55 +85,6 @@
-
-
- - {{ $t('settings.language') }} - -
- -
- {{ $t('settings.theme') }} -
- - -
-
-
(version ?? '').replace(/^ const normalizedServerVersion = computed(() => normalizeVersion(serverVersion.value)) const settingsStore = useSettingsStore() -const { language, theme } = storeToRefs(settingsStore) -const { setLanguage, setTheme } = settingsStore const isDark = computed(() => settingsStore.theme === 'dark') const checking = ref(false) diff --git a/apps/web/src/pages/appearance/index.vue b/apps/web/src/pages/appearance/index.vue new file mode 100644 index 000000000..b42d5fc22 --- /dev/null +++ b/apps/web/src/pages/appearance/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/apps/web/src/pages/bots/components/bot-access.vue b/apps/web/src/pages/bots/components/bot-access.vue index 4b670bb26..7c20d3fef 100644 --- a/apps/web/src/pages/bots/components/bot-access.vue +++ b/apps/web/src/pages/bots/components/bot-access.vue @@ -31,15 +31,14 @@ type="button" class="rounded-lg border p-4 text-left transition-colors" :class="defaultEffectDraft === 'allow' - ? 'border-foreground bg-foreground text-background' + ? 'border-ring bg-secondary text-foreground ring-1 ring-ring/20' : 'border-border bg-background text-foreground hover:bg-accent'" :disabled="isSavingDefaultEffect" @click="handleSetDefaultEffect('allow')" > {{ $t('bots.access.blacklistMode') }} {{ $t('bots.access.blacklistModeDescription') }} @@ -48,15 +47,14 @@ type="button" class="rounded-lg border p-4 text-left transition-colors" :class="defaultEffectDraft === 'deny' - ? 'border-foreground bg-foreground text-background' + ? 'border-ring bg-secondary text-foreground ring-1 ring-ring/20' : 'border-border bg-background text-foreground hover:bg-accent'" :disabled="isSavingDefaultEffect" @click="handleSetDefaultEffect('deny')" > {{ $t('bots.access.whitelistMode') }} {{ $t('bots.access.whitelistModeDescription') }} @@ -142,7 +140,7 @@

{{ rule.enabled ? $t('bots.access.ruleEnabled') : $t('bots.access.ruleDisabled') }} @@ -406,8 +404,8 @@ type="button" class="rounded-md border px-2 py-1.5 text-xs font-medium transition-colors text-center" :class="ruleForm.sourceConversationType === scope.value - ? 'border-foreground bg-foreground text-background' - : 'border-border text-muted-foreground hover:bg-accent'" + ? 'border-ring bg-secondary text-foreground ring-1 ring-ring/20' + : 'border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground'" @click="setChatScope(scope.value)" > {{ scope.label }} diff --git a/apps/web/src/pages/bots/components/bot-channels.vue b/apps/web/src/pages/bots/components/bot-channels.vue index e8e677d53..013189c5e 100644 --- a/apps/web/src/pages/bots/components/bot-channels.vue +++ b/apps/web/src/pages/bots/components/bot-channels.vue @@ -53,7 +53,7 @@
{{ $t('bots.channels.statusActive') }} diff --git a/apps/web/src/pages/bots/components/bot-container.vue b/apps/web/src/pages/bots/components/bot-container.vue index bf2db9d49..18c32c270 100644 --- a/apps/web/src/pages/bots/components/bot-container.vue +++ b/apps/web/src/pages/bots/components/bot-container.vue @@ -730,7 +730,7 @@ watch([activeTab, botId], ([tab]) => {
{{ $t('bots.container.botNotReady') }}
@@ -873,9 +873,9 @@ watch([activeTab, botId], ([tab]) => { >
-

+

{{ $t('bots.container.legacyWarning') }}