Phase 002: rebrand to hermes-ui and consolidate i18n to zh/en
Build / build (push) Canceled after 0s
NPM Lockfile Check / npm ci --ignore-scripts (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

Removes the ekko / hermes-web-ui branding from the whole tree and drops
eight locale files, so the project presents one name and one language pair.

Data compatibility is deliberate, not accidental: DB filenames, the JWT
audience, and skill manifest ownership all still recognise their pre-rename
values so existing deployments under ~/.hermes-web-ui keep working without a
re-login or a re-injection.

scripts/harness-check.mjs now scans for the legacy strings and fails the
build on any new occurrence, with an explicit allow-list (and reasons) for
the compatibility paths that must keep them.
This commit is contained in:
2026-10-16 11:11:29 +08:00
parent 1e9ea89d86
commit c93e1fe1bd
70 changed files with 1305 additions and 17856 deletions
@@ -350,7 +350,7 @@ function handleUpdateClick() {
</div>
<div class="version-info">
<div class="version-links">
<a class="sidebar-footer-link" href="https://github.com/EKKOLearnAI/hermes-studio" target="_blank" rel="noopener noreferrer" title="GitHub">
<a class="sidebar-footer-link" href="https://gitea.erhe.top/toor/hermes-ui" target="_blank" rel="noopener noreferrer" title="GitHub">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
</a>
<a class="sidebar-footer-link" href="https://hermes-studio.ai/" target="_blank" rel="noopener noreferrer" title="Website">
@@ -50,7 +50,7 @@ onMounted(() => {
<div class="desktop-titlebar__drag">
<div class="desktop-titlebar__brand">
<img class="desktop-titlebar__logo" src="/logo.png" alt="" draggable="false">
<span class="desktop-titlebar__title">Hermes Studio</span>
<span class="desktop-titlebar__title">hermes-ui</span>
</div>
</div>
<div v-if="showWindowButtons" class="desktop-titlebar__controls" @dblclick.stop>
@@ -7,15 +7,7 @@ const { locale } = useI18n()
const options = [
{ label: '简体中文', value: 'zh' },
{ label: '繁體中文', value: 'zh-TW' },
{ label: 'English', value: 'en' },
{ label: '日本語', value: 'ja' },
{ label: '한국어', value: 'ko' },
{ label: 'Français', value: 'fr' },
{ label: 'Español', value: 'es' },
{ label: 'Deutsch', value: 'de' },
{ label: 'Português', value: 'pt' },
{ label: 'Русский', value: 'ru' },
]
function handleChange(val: string) {
+2 -67
View File
@@ -4,80 +4,15 @@ export interface ChangelogEntry {
changes: string[]
}
// Entries reference `changelog.*` i18n keys, so every listed key must exist in
// both zh.ts and en.ts. Start a new entry when releasing.
export const changelog: ChangelogEntry[] = [
{
version: '0.6.29',
date: '2026-07-12',
changes: [
'changelog.new_0_6_29_1',
'changelog.new_0_6_29_2',
'changelog.new_0_6_29_3',
'changelog.new_0_6_29_4',
'changelog.new_0_6_29_5',
'changelog.new_0_6_29_6',
'changelog.new_0_6_29_7',
'changelog.new_0_6_29_8',
'changelog.new_0_6_29_9',
'changelog.new_0_6_29_10',
],
},
{
version: '0.6.28',
date: '2026-07-10',
changes: [
'changelog.new_0_6_28_1',
'changelog.new_0_6_28_2',
'changelog.new_0_6_28_3',
'changelog.new_0_6_28_4',
'changelog.new_0_6_28_5',
'changelog.new_0_6_28_6',
'changelog.new_0_6_28_7',
'changelog.new_0_6_28_8',
'changelog.new_0_6_28_9',
],
},
{
version: '0.6.27',
date: '2026-07-08',
changes: [
'changelog.new_0_6_27_1',
'changelog.new_0_6_27_2',
'changelog.new_0_6_27_3',
'changelog.new_0_6_27_4',
'changelog.new_0_6_27_5',
'changelog.new_0_6_27_6',
'changelog.new_0_6_27_7',
],
},
{
version: '0.6.26',
date: '2026-07-06',
changes: [
'changelog.new_0_6_26_1',
'changelog.new_0_6_26_2',
'changelog.new_0_6_26_3',
'changelog.new_0_6_26_4',
'changelog.new_0_6_26_5',
'changelog.new_0_6_26_6',
],
},
{
version: '0.6.25',
date: '2026-07-03',
changes: [
'changelog.new_0_6_25_1',
],
},
{
version: '0.6.24',
date: '2026-07-03',
changes: [
'changelog.new_0_6_24_1',
'changelog.new_0_6_24_2',
'changelog.new_0_6_24_3',
'changelog.new_0_6_24_4',
'changelog.new_0_6_24_5',
'changelog.new_0_6_24_6',
],
},
]
+9 -21
View File
@@ -4,33 +4,21 @@ import type { SupportedLocale } from './messages'
const saved = localStorage.getItem('hermes_locale')
function normalize(tag: string): SupportedLocale {
return tag.toLowerCase().startsWith('zh') ? 'zh' : 'en'
}
function resolveLocale(saved: string | null): SupportedLocale {
if (saved && (supportedLocales as readonly string[]).includes(saved)) {
return saved as SupportedLocale
}
function normalize(tag: string): SupportedLocale | null {
const lower = tag.toLowerCase()
if (lower.startsWith('zh')) {
const isTraditional =
lower.includes('hant') ||
lower.includes('-tw') ||
lower.includes('-hk') ||
lower.includes('-mo')
return isTraditional ? 'zh-TW' : 'zh'
}
const short = tag.slice(0, 2)
if ((supportedLocales as readonly string[]).includes(tag)) return tag as SupportedLocale
if ((supportedLocales as readonly string[]).includes(short)) return short as SupportedLocale
return null
}
// A locale stored before the zh/en-only consolidation (for example 'zh-TW'
// or 'ja') is no longer supported; remap it instead of dropping to English.
if (saved) return normalize(saved)
for (const lang of navigator.languages) {
const resolved = normalize(lang)
if (resolved) return resolved
}
return 'en'
const preferred = navigator.languages[0]
return preferred ? normalize(preferred) : 'en'
}
function setHtmlLang(locale: SupportedLocale) {
File diff suppressed because it is too large Load Diff
+3 -40
View File
@@ -227,7 +227,7 @@ export default {
disconnected: 'Disconnected',
collapse: 'Collapse menu',
expand: 'Expand menu',
updateTip: 'Run "hermes-web-ui update" in terminal to update',
updateTip: 'Run "hermes-ui update" in terminal to update',
updateVersion: 'Upgrade to v{version}',
reloadClientVersion: 'Reload for v{version}',
updating: 'Updating...',
@@ -2426,44 +2426,7 @@ export default {
// Changelog
changelog: {
new_0_6_29_1: 'This release covers all 14 PRs merged after 0.6.28, spanning visual consistency, MCU audio and v2 hardware, workspace diffs, model usage analytics, Docker updates, and MiMo voice cloning',
new_0_6_29_2: 'Shared page headers, dark surfaces, and radius tokens now follow one visual system, while version previews sort valid tags by semantic version (#2014, #2015)',
new_0_6_29_3: 'The ESP32 product page has a responsive redesign, and the website version selector now includes Hermes Agent 0.18.2 (#2020, #2042)',
new_0_6_29_4: 'ESP32-C3 v1 uplink audio now uses IMA-ADPCM at about one quarter of the bandwidth with a two-minute recording limit, and the OLED returns to the LAN IP after transient states (#2024, #2025)',
new_0_6_29_5: 'Workspace diffs start collapsed and suppress zero-line changes, while the standard dark chat composer background is restored (#2032)',
new_0_6_29_6: 'Session exports tolerate malformed percent signs in filenames, and Docker upgrades now show image pull and recreate guidance instead of modifying npm inside the container (#2026, #2033)',
new_0_6_29_7: 'Bridge tool results remain fully persisted but are projected to a 5,500-character head/tail summary in model context to prevent repeated context amplification (#2036)',
new_0_6_29_8: 'Usage analytics now unify model calls from Hermes Agent, Claude Code, Codex, and Ekko Agent and add an Agent distribution view (#2039)',
new_0_6_29_9: 'A separate ESP32-C3 v2 firmware target adds the new hardware pin map without affecting v1; v2 defaults to 80% volume with 1.5x PCM gain and soft limiting (#2044, #2045)',
new_0_6_29_10: 'MiMo voice cloning restores WAV/MP3 reference-audio upload and sends the reference through audio.voice as required by the current API (#2046)',
new_0_6_28_1: 'This release covers every PR merged after 0.6.27, including MCU management, group chat workspace diffs, coding-agent fixes, workflow approvals, workspace diff filtering, scheduled-job model selection, and desktop version update hints',
new_0_6_28_2: 'MCU support adds device management, requires API-token authorization for global-agent events, refreshes the ESP32-C3 firmware, and stabilizes remote relay login',
new_0_6_28_3: 'Group Chat workspace changes now persist as workspace diff runs so room-level agent activity can be reviewed after the session completes',
new_0_6_28_4: 'Coding agents now inherit scoped external MCP servers, route aborts to the right workflow agent, and best-effort export completed Codex and Claude Code native sessions',
new_0_6_28_5: 'Session polish fixes the workspace file drawer, removes broken starred-history embeds, and records bridge session ended_at and end_reason metadata',
new_0_6_28_6: 'Workflow nodes now support approval gates after node completion, with rerun approval states corrected for repeated workflow execution',
new_0_6_28_7: 'Workspace diff cards filter SQLite WAL/SHM sidecars and keep scanning past unchanged files so newly created ordinary files stay visible',
new_0_6_28_8: 'Scheduled jobs can choose and persist provider/model settings without passing unsupported model flags to the current Hermes cron CLI',
new_0_6_28_9: 'Desktop version management now shows a localized new-version hint on its button when an update is available, and the public version manifest includes 0.6.28',
new_0_6_27_1: 'This release covers every PR merged after 0.6.26, including desktop startup fallback, Windows runtime fixes, remote MCU relay, group chat workspaces, default workspace polish, and empty diff filtering',
new_0_6_27_2: 'Desktop startup now falls back to the bundled Web UI when the active Web UI directory is missing or incomplete, while preserving the pinned runtime',
new_0_6_27_3: 'Windows desktop runtimes now use relocatable hermes.cmd wrappers instead of fragile .exe trampolines for Web UI server invocations',
new_0_6_27_4: 'Remote MCU traffic now uses the Socket.IO /global-agent relay, with steadier ESP32-C3 login, discovery, voice playback queueing, Wi-Fi credential persistence, and updated firmware',
new_0_6_27_5: 'Group Chat rooms can now own a validated workspace that is passed into group-agent runs, with read-only members seeing workspace, invite code, and management controls redacted',
new_0_6_27_6: 'Default workspace favorites now sit inside the folder picker selected-path row, with completed locale coverage for the workspace picker labels',
new_0_6_27_7: 'Workspace diff cards now skip zero-byte changes with no patch or line delta, avoiding meaningless +0/-0 changed-file cards',
new_0_6_26_1: 'This release covers the PRs merged after 0.6.25, focusing on Ekko Agent, Coding Agent context display, Journey, group chat baselines, desktop fixes, and MCP payload size',
new_0_6_26_2: 'Ekko Agent is now available in chat with runtime context handling, browser tools, steadier tool events, and restored tool-result history for context usage accounting',
new_0_6_26_3: 'Coding Agent sessions now show reasoning and context settings more clearly, persist API mode, avoid Codex app-server mode misuse, and keep context usage from jumping after follow-up turns',
new_0_6_26_4: 'Journey gained a learning graph view with polished graph interactions, while group chat added baseline coverage for the new feature line',
new_0_6_26_5: 'Desktop reliability improved with login persistence/reset fixes, Windows worker dynamic port fallback, stdout/stderr EPIPE handling, and isolated ESP32-C3 v1 firmware packaging',
new_0_6_26_6: 'Hermes Studio MCP now returns a compact available-models summary by default, with filters and an explicit full-detail option to avoid flooding agent context',
new_0_6_25_1: 'Fixed execute_code failures with Hermes Agent 0.18.0 by forwarding the new has_host_access guard parameter through the Agent Bridge approval memory wrapper',
new_0_6_24_1: 'This release covers the PRs merged after 0.6.23, focusing on chat input polish, workspace visibility, plugin management, model defaults, and workspace diff review',
new_0_6_24_2: 'Chat and group chat inputs now use a refreshed toolbar with compact model controls, grouped input settings, clearer context usage, mobile-friendly chrome, and restored manual single-chat input resizing',
new_0_6_24_3: 'Workspace run changes now appear as durable chat diff cards with a right-side diff/editor drawer, restored after session resume and tuned for mobile viewing',
new_0_6_24_4: 'The workspace folder picker can browse Windows drive and junction workflows more reliably and now shows dot-prefixed folders such as .hermes, .config, and .codex while keeping WORKSPACE_BASE safety checks',
new_0_6_24_5: 'Plugins can be enabled or disabled from Studio when they are manageable user plugins, and the Plugins page now reflects base and profile plugin state more accurately',
new_0_6_24_6: 'Models now support default provider and default model actions; bridge terminal error detection is less noisy, Hermes Agent runtime packaging moves to 0.18.0, and the website version manifest was refreshed',
new_0_6_29_9: 'The project is now hermes-ui: the Electron desktop app, marketing site, and ESP32 firmware are removed, leaving only the web client and server',
new_0_6_29_10: 'UI branding is unified under hermes-ui, and locales are consolidated to Simplified Chinese and English',
},
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -40
View File
@@ -227,7 +227,7 @@ export default {
disconnected: '未连接',
collapse: '收起菜单',
expand: '展开菜单',
updateTip: '在终端运行 "hermes-web-ui update" 即可更新',
updateTip: '在终端运行 "hermes-ui update" 即可更新',
updateVersion: '升级版本 v{version}',
reloadClientVersion: '刷新到 v{version}',
updating: '正在更新...',
@@ -2428,44 +2428,7 @@ export default {
// 更新日志
changelog: {
new_0_6_29_1: '本版本汇总 0.6.28 之后合并的 14 个 PR,涵盖界面统一、MCU 音频与 v2 硬件、workspace diff、模型用量统计、Docker 更新和 MiMo 音色复刻',
new_0_6_29_2: '统一页面标题、暗色卡片和圆角等视觉规范;版本预览会按语义版本倒序展示有效标签(#2014、#2015)',
new_0_6_29_3: 'ESP32 产品介绍页完成响应式改版,网站版本选择器新增 Hermes Agent 0.18.2(#2020、#2042)',
new_0_6_29_4: 'ESP32-C3 v1 上行语音改用 IMA-ADPCM,带宽降至约四分之一、录音上限增至 2 分钟;OLED 状态结束后会恢复显示局域网 IP(#2024、#2025)',
new_0_6_29_5: 'Workspace diff 默认折叠并过滤零行变更,同时恢复聊天输入框的标准暗色背景(#2032)',
new_0_6_29_6: '会话导出可兼容含异常百分号的文件名;Docker 用户点击升级时会看到镜像拉取与重建指引,而不会修改容器内 npm(#2026、#2033)',
new_0_6_29_7: 'Bridge 工具结果仍会完整保存,但进入模型上下文时限制为 5,500 字符的头尾摘要,避免大结果持续放大上下文(#2036)',
new_0_6_29_8: '用量分析统一记录 Hermes Agent、Claude Code、Codex 和 Ekko Agent 的模型调用,并新增 Agent 分布视图(#2039)',
new_0_6_29_9: '新增独立的 ESP32-C3 v2 固件与硬件引脚配置,保持 v1 隔离;v2 默认音量 80%,采用 1.5 倍 PCM 增益和软限幅提升响度(#2044、#2045)',
new_0_6_29_10: 'MiMo 音色复刻恢复 WAV/MP3 参考音频上传,并按当前 API 要求通过 audio.voice 发送参考音频(#2046)',
new_0_6_28_1: '本版本汇总 0.6.27 之后合并的 PR:MCU 管理、群聊 workspace diff、Coding Agent 修复、Workflow 审批、workspace diff 过滤、定时任务模型选择和桌面端版本提示都已纳入',
new_0_6_28_2: 'MCU 支持新增设备管理,global-agent 事件要求 API token 授权,ESP32-C3 固件已刷新,远程 relay 登录也更稳定',
new_0_6_28_3: '群聊 workspace 变更会持久化为 workspace diff run,房间级 agent 活动在会话结束后仍可回看',
new_0_6_28_4: 'Coding Agent 会继承 scoped external MCP server,workflow agent abort 路由更准确,并会尽力导出完成后的 Codex 与 Claude Code 原生会话',
new_0_6_28_5: '会话体验修复了 workspace 文件抽屉、移除损坏的星标历史 embed,并记录 bridge 会话 ended_at 和 end_reason 元数据',
new_0_6_28_6: 'Workflow 节点支持完成后的审批门禁,重复执行时的审批重跑状态也已修正',
new_0_6_28_7: 'Workspace diff 会过滤 SQLite WAL/SHM sidecar,并跳过未变化文件继续扫描,确保新建的普通文件不会被隐藏',
new_0_6_28_8: '定时任务支持选择并持久化 provider/model,同时不会把当前 Hermes cron CLI 不支持的模型参数传给 CLI',
new_0_6_28_9: '桌面端版本管理按钮会在有更新时显示国际化的「有新版」提示,公开版本清单也加入 0.6.28',
new_0_6_27_1: '本版本汇总 0.6.26 之后合并的 PR:桌面端启动兜底、Windows runtime、远程 MCU、群聊工作区、默认工作区收藏和空 diff 过滤都已纳入',
new_0_6_27_2: '桌面端启动更稳:当 active Web UI 目录缺失或损坏时,会清理失效 Web UI 指针并回退到内置 Web UI,同时保留 runtime pin',
new_0_6_27_3: 'Windows 桌面 runtime 改用可迁移的 hermes.cmd wrapper,避免旧 .exe trampoline 在 Web UI server 调用时失效',
new_0_6_27_4: '远程 MCU 改走 Socket.IO /global-agent relay,ESP32-C3 登录、发现、远程机器识别、语音播放队列和 Wi-Fi 凭据保存更稳定,固件也已更新',
new_0_6_27_5: '群聊支持创建和设置房间工作区,服务端校验 workspace,群聊 agent run 会继承该工作区;只读成员会隐藏 workspace、inviteCode 和管理入口',
new_0_6_27_6: '默认工作区收藏星标移动到文件夹选择器的已选路径行右侧,并补齐各语言文案,工作区选择界面更自然',
new_0_6_27_7: '工作区 diff 会跳过前后都是 0 字节且没有 patch 或行变化的空文件变更,避免出现 +0/-0 的无意义 diff 卡片',
new_0_6_26_1: '本版本汇总 0.6.25 之后合并的 PR,重点覆盖 Ekko Agent、Coding Agent 上下文展示、Journey、群聊基线、桌面端修复和 MCP 返回体收敛',
new_0_6_26_2: 'Ekko Agent 接入聊天链路,补齐 runtime context、浏览器工具、更稳定的工具事件,并恢复 tool result 历史用于上下文用量统计',
new_0_6_26_3: 'Coding Agent 会话现在更清楚地展示 reasoning 与 context 设置,持久化 API mode,避免 Codex 误用 app-server API mode,并修复继续对话后上下文用量跳变',
new_0_6_26_4: 'Journey 新增学习图谱视图并打磨图谱交互,群聊新功能线也补上基线测试覆盖',
new_0_6_26_5: '桌面端可靠性继续提升:修复登录持久化和重置流程、Windows worker 动态端口兜底、stdout/stderr EPIPE 处理,并隔离 ESP32-C3 v1 固件打包',
new_0_6_26_6: 'Hermes Studio MCP 的 available-models 默认改为紧凑摘要,支持过滤和显式完整详情参数,避免把大量模型数据塞进 agent 上下文',
new_0_6_25_1: '修复 Hermes Agent 0.18.0 下 execute_code 因 Agent Bridge 审批记忆 wrapper 未转发新的 has_host_access 参数而失败的问题',
new_0_6_24_1: '本版本汇总 0.6.23 之后合并的 PR,重点改进聊天输入区、工作区可见性、插件管理、默认模型和工作区 diff 查看体验',
new_0_6_24_2: '单聊和群聊输入区更新为整合式工具栏,模型选择更紧凑,输入设置集中管理,上下文用量更清晰,移动端样式更稳,并恢复单聊输入框手动拖拽高度',
new_0_6_24_3: '工作区运行变更会以持久的聊天 diff 卡片展示,右侧抽屉支持查看 diff 和编辑文件,恢复会话后仍能保留,并优化了移动端查看体验',
new_0_6_24_4: '工作区目录选择器更可靠地支持 Windows 磁盘和 junction 场景,并会显示 .hermes、.config、.codex 等点前缀目录,同时保留 WORKSPACE_BASE 安全边界',
new_0_6_24_5: 'Plugins 页面支持启用或禁用可管理的用户插件,并能更准确地合并显示 Hermes 基础配置和当前 profile 的插件状态',
new_0_6_24_6: 'Models 页面新增设置默认 provider 和默认模型操作;bridge 终端错误识别减少误报,Hermes Agent runtime 打包升级到 0.18.0,官网版本清单也已刷新',
new_0_6_29_9: '项目更名为 hermes-ui:移除 Electron 桌面端、营销官网与 ESP32 固件,只保留 Web 与服务端',
new_0_6_29_10: '界面品牌文案统一为 hermes-ui,语言包收敛为简体中文与 English 两项',
},
}
+2 -10
View File
@@ -1,17 +1,9 @@
import en from './locales/en'
import zh from './locales/zh'
import zhTW from './locales/zh-TW'
import ja from './locales/ja'
import ko from './locales/ko'
import fr from './locales/fr'
import es from './locales/es'
import de from './locales/de'
import pt from './locales/pt'
import ru from './locales/ru'
export type LocaleMessages = Record<string, any>
export const supportedLocales = ['en', 'zh', 'zh-TW', 'ja', 'ko', 'fr', 'es', 'de', 'pt', 'ru'] as const
export const supportedLocales = ['en', 'zh'] as const
export type SupportedLocale = (typeof supportedLocales)[number]
function isPlainObject(value: unknown): value is LocaleMessages {
@@ -34,7 +26,7 @@ export function mergeMessagesWithFallback(
return merged
}
const rawMessages: Record<string, LocaleMessages> = { en, zh, 'zh-TW': zhTW, ja, ko, fr, es, de, pt, ru }
const rawMessages: Record<string, LocaleMessages> = { en, zh }
export const messages: Record<string, LocaleMessages> = {}
for (const [locale, msg] of Object.entries(rawMessages)) {
+2 -2
View File
@@ -96,9 +96,9 @@ async function handlePasswordLogin() {
</template>
<template v-else>
<span>{{ t("login.lockResetHint") }}</span>
<code>hermes-web-ui clear-login-locks --restart</code>
<code>hermes-ui clear-login-locks --restart</code>
<span>{{ t("login.defaultLoginResetHint") }}</span>
<code>hermes-web-ui reset-default-login</code>
<code>hermes-ui reset-default-login</code>
</template>
</div>
<button type="submit" class="login-btn" :disabled="loading">
@@ -24,7 +24,7 @@ const routeProfile = computed(() => {
return typeof value === 'string' && value.trim() ? value : null
})
const productTitle = 'Hermes Studio'
const productTitle = 'hermes-ui'
const tabTitle = computed(() => {
if (route.name !== 'hermes.session') return productTitle
return chatStore.activeSession?.title?.trim() || productTitle