mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 20:30:00 +08:00
refactor(hermes): emoji → SVG 图标,统一视觉语言
## 问题 新加的 hermes 页面里到处是 emoji(⚠️📁📋💬🔐🔗🖼️📝⚙️📦💬🔊🎙🔍✓ 等), 不同 OS 渲染样式差异大(macOS Apple Color Emoji vs Windows Segoe UI Emoji vs Linux Noto Color Emoji),看着很不专业,也跟现有 SVG 图标系统割裂。 ## 方案 新建 src/engines/hermes/lib/svg-icons.js — 集中所有需要的 SVG path: 状态:alert-triangle, check, x, check-circle, x-circle, info 文件:folder, folder-up, file, file-text, image, link-2, settings 列表:clipboard-list, message-square, inbox 安全:lock, shield, key 媒体:volume, mic, search 导出 svgIcon(name, opts) — 渲染时统一 viewBox / stroke-width / currentColor, size 可选覆盖。 ## 替换覆盖(7 个页面 + 2 处 CSS) - profiles.js: ⚠️ → alert-triangle, 📁 → folder - kanban.js: ⚠️ → alert-triangle, 📋 → clipboard-list, 💬 → message-square - oauth.js: ⚠️ → alert-triangle, 🔐 → lock - group-chat.js: ⚠️ → alert-triangle - logs.js: ⚠️ → 纯文本 [ERROR](log raw 字段不需要图标) - files.js: 📁🔗🖼️📝⚙️📄 全部 → folder/link-2/image/file-text/settings/file ".." → folder-up - lazy-deps.js: 7 个 category emoji → message-square/volume/mic/search/ shield/inbox/image ✓ 装好标识 → check svg 📦 empty → inbox CSS 适配: - .page-inline-error-icon: 加 inline-flex + 自动 currentColor,svg 20×20 - .empty-state .empty-icon: 加 inline-flex + svg 跟 font-size 走(1em) ## 范围控制 本次只处理我最近 3 个 commit 里新加的页面 emoji。 历史代码(setup.js / services.js / chat.js 的 ✅❌✓ 等)暂不动 — 那是 跨工作流的复杂改动,单独评估再做,避免连锁影响 CI。 ## 验证 ✓ npm run build PASS(1.80s)
This commit is contained in:
72
src/engines/hermes/lib/svg-icons.js
Normal file
72
src/engines/hermes/lib/svg-icons.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Hermes 页面共用 SVG 图标集 — 替换分散的 emoji,统一视觉语言。
|
||||
*
|
||||
* 命名约定:
|
||||
* - 名字与 lucide / feather 一致,方便后续替换
|
||||
* - 所有 path 默认使用 currentColor + fill:none + stroke-width:1.6
|
||||
* - viewBox 全部 0 0 24 24,渲染时由调用方提供 width/height + 容器颜色
|
||||
*
|
||||
* 用法:
|
||||
* import { svgIcon } from '../lib/svg-icons.js'
|
||||
* svgIcon('folder', { size: 18, className: 'hm-files-icon' })
|
||||
*/
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const PATHS = {
|
||||
// === 通用 / 状态 ===
|
||||
'alert-triangle': '<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
|
||||
'check': '<polyline points="20 6 9 17 4 12"/>',
|
||||
'x': '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
|
||||
'check-circle': '<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
||||
'x-circle': '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>',
|
||||
'info': '<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>',
|
||||
|
||||
// === 文件系统 ===
|
||||
'folder': '<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>',
|
||||
'folder-up': '<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/><polyline points="9 14 12 11 15 14"/><line x1="12" y1="11" x2="12" y2="17"/>',
|
||||
'file': '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/>',
|
||||
'file-text': '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
|
||||
'image': '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>',
|
||||
'link-2': '<path d="M15 7h3a5 5 0 015 5 5 5 0 01-5 5h-3m-6 0H6a5 5 0 01-5-5 5 5 0 015-5h3"/><line x1="8" y1="12" x2="16" y2="12"/>',
|
||||
'settings': '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>',
|
||||
|
||||
// === 列表 / 看板 ===
|
||||
'clipboard-list': '<path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/>',
|
||||
'message-square': '<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>',
|
||||
'inbox': '<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11L2 12v6a2 2 0 002 2h16a2 2 0 002-2v-6l-3.45-6.89A2 2 0 0016.76 4H7.24a2 2 0 00-1.79 1.11z"/>',
|
||||
|
||||
// === 安全 / 认证 ===
|
||||
'lock': '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/>',
|
||||
'shield': '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
|
||||
'key': '<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>',
|
||||
|
||||
// === 媒体 ===
|
||||
'volume': '<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"/>',
|
||||
'mic': '<path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/>',
|
||||
'search': '<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染指定图标为 inline SVG 字符串(可直接放进 innerHTML)。
|
||||
*
|
||||
* @param {string} name - PATHS 里的 key
|
||||
* @param {object} [opts]
|
||||
* @param {number} [opts.size=14] - 边长
|
||||
* @param {number} [opts.stroke=1.6] - stroke-width
|
||||
* @param {string} [opts.className] - 额外 class
|
||||
* @param {string} [opts.color] - 强制颜色(不传则跟随 currentColor)
|
||||
*/
|
||||
export function svgIcon(name, opts = {}) {
|
||||
const path = PATHS[name]
|
||||
if (!path) return ''
|
||||
const size = opts.size ?? 14
|
||||
const stroke = opts.stroke ?? 1.6
|
||||
const cls = opts.className ? ` class="${opts.className}"` : ''
|
||||
const color = opts.color ? ` stroke="${opts.color}"` : ''
|
||||
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"${color} stroke-width="${stroke}" stroke-linecap="round" stroke-linejoin="round" width="${size}" height="${size}"${cls}>${path}</svg>`
|
||||
}
|
||||
|
||||
/** 直接拿原 path 字符串(用于场景需要自定义 svg 包装的情况) */
|
||||
export function svgPath(name) {
|
||||
return PATHS[name] || ''
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { api } from '../../../lib/tauri-api.js'
|
||||
import { toast } from '../../../components/toast.js'
|
||||
import { showConfirm } from '../../../components/modal.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||
@@ -35,14 +36,13 @@ function formatTime(secs) {
|
||||
}
|
||||
|
||||
function iconForKind(kind, name) {
|
||||
if (kind === 'dir') return '📁'
|
||||
if (kind === 'symlink') return '🔗'
|
||||
if (kind === 'dir') return svgIcon('folder', { size: 14 })
|
||||
if (kind === 'symlink') return svgIcon('link-2', { size: 14 })
|
||||
const ext = (name.split('.').pop() || '').toLowerCase()
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext)) return '🖼️'
|
||||
if (['md', 'txt'].includes(ext)) return '📝'
|
||||
if (['json', 'yaml', 'yml', 'toml'].includes(ext)) return '⚙️'
|
||||
if (['py', 'js', 'ts', 'rs', 'go'].includes(ext)) return '📄'
|
||||
return '📄'
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext)) return svgIcon('image', { size: 14 })
|
||||
if (['md', 'txt'].includes(ext)) return svgIcon('file-text', { size: 14 })
|
||||
if (['json', 'yaml', 'yml', 'toml'].includes(ext)) return svgIcon('settings', { size: 14 })
|
||||
return svgIcon('file', { size: 14 })
|
||||
}
|
||||
|
||||
export function render() {
|
||||
@@ -118,7 +118,7 @@ export function render() {
|
||||
}
|
||||
return `
|
||||
<div class="hm-files-list">
|
||||
${currentDir ? `<div class="hm-files-entry hm-files-entry--up" data-cd="${escAttr(parentDir(currentDir))}"><span class="hm-files-icon">📁</span><span class="hm-files-name">..</span></div>` : ''}
|
||||
${currentDir ? `<div class="hm-files-entry hm-files-entry--up" data-cd="${escAttr(parentDir(currentDir))}"><span class="hm-files-icon">${svgIcon('folder-up', { size: 14 })}</span><span class="hm-files-name">..</span></div>` : ''}
|
||||
${entries.map(e => `
|
||||
<div class="hm-files-entry ${e.kind === 'dir' ? 'is-dir' : 'is-file'} ${selectedRel === joinRel(currentDir, e.name) ? 'is-selected' : ''}"
|
||||
data-kind="${escAttr(e.kind)}"
|
||||
|
||||
@@ -15,6 +15,7 @@ import { t } from '../../../lib/i18n.js'
|
||||
import { api, isTauriRuntime } from '../../../lib/tauri-api.js'
|
||||
import { toast } from '../../../components/toast.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||
@@ -131,7 +132,7 @@ export function render() {
|
||||
if (m.error) {
|
||||
return `
|
||||
<div class="hm-gc-msg hm-gc-msg--assistant hm-gc-msg--error">
|
||||
<div class="hm-gc-msg-meta">${fromTag} <span style="color:var(--error)">⚠️ ${escHtml(t('engine.hermesGroupChatRunFailed'))}</span></div>
|
||||
<div class="hm-gc-msg-meta">${fromTag} <span style="color:var(--error);display:inline-flex;align-items:center;gap:4px">${svgIcon('alert-triangle', { size: 12 })} ${escHtml(t('engine.hermesGroupChatRunFailed'))}</span></div>
|
||||
<div class="hm-gc-msg-bubble" style="color:var(--error)">${escHtml(m.error)}</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -18,6 +18,7 @@ import { api } from '../../../lib/tauri-api.js'
|
||||
import { toast } from '../../../components/toast.js'
|
||||
import { showModal, showContentModal } from '../../../components/modal.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
const KANBAN_BASE = '/api/plugins/kanban'
|
||||
|
||||
@@ -30,7 +31,7 @@ function renderInlineError(err) {
|
||||
const h = humanizeError(err, t('engine.hermesKanbanTaskLoadFailed'))
|
||||
return `
|
||||
<div class="page-inline-error">
|
||||
<div class="page-inline-error-icon">⚠️</div>
|
||||
<div class="page-inline-error-icon">${svgIcon('alert-triangle', { size: 20 })}</div>
|
||||
<div class="page-inline-error-body">
|
||||
<div class="page-inline-error-message">${escHtml(h.message)}</div>
|
||||
${h.hint ? `<div class="page-inline-error-hint">${escHtml(h.hint)}</div>` : ''}
|
||||
@@ -77,7 +78,7 @@ export function render() {
|
||||
|
||||
function renderBoard() {
|
||||
if (!board?.columns?.length) {
|
||||
return `<div class="empty-state empty-compact"><div class="empty-icon">📋</div><div class="empty-title">${escHtml(t('engine.hermesKanbanEmpty'))}</div></div>`
|
||||
return `<div class="empty-state empty-compact"><div class="empty-icon">${svgIcon('clipboard-list', { size: 32 })}</div><div class="empty-title">${escHtml(t('engine.hermesKanbanEmpty'))}</div></div>`
|
||||
}
|
||||
return `
|
||||
<div class="hm-kanban-board">
|
||||
@@ -107,7 +108,7 @@ export function render() {
|
||||
<div class="hm-kanban-task-meta">
|
||||
${priorityBadge}
|
||||
${assignee}
|
||||
${task.comment_count ? `<span class="hm-kanban-task-meta-item">💬 ${task.comment_count}</span>` : ''}
|
||||
${task.comment_count ? `<span class="hm-kanban-task-meta-item">${svgIcon('message-square', { size: 12 })} ${task.comment_count}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -11,16 +11,17 @@ import { t } from '../../../lib/i18n.js'
|
||||
import { api } from '../../../lib/tauri-api.js'
|
||||
import { toast } from '../../../components/toast.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
// feature 分类配置(决定分组顺序 + 图标 + 文案)
|
||||
const CATEGORIES = [
|
||||
{ prefix: 'platform.', emoji: '💬', titleKey: 'hermesLazyDeps.catPlatform' },
|
||||
{ prefix: 'tts.', emoji: '🔊', titleKey: 'hermesLazyDeps.catTts' },
|
||||
{ prefix: 'stt.', emoji: '🎙️', titleKey: 'hermesLazyDeps.catStt' },
|
||||
{ prefix: 'search.', emoji: '🔍', titleKey: 'hermesLazyDeps.catSearch' },
|
||||
{ prefix: 'provider.', emoji: '🧠', titleKey: 'hermesLazyDeps.catProvider' },
|
||||
{ prefix: 'memory.', emoji: '🗂️', titleKey: 'hermesLazyDeps.catMemory' },
|
||||
{ prefix: 'image.', emoji: '🎨', titleKey: 'hermesLazyDeps.catImage' },
|
||||
{ prefix: 'platform.', icon: 'message-square', titleKey: 'hermesLazyDeps.catPlatform' },
|
||||
{ prefix: 'tts.', icon: 'volume', titleKey: 'hermesLazyDeps.catTts' },
|
||||
{ prefix: 'stt.', icon: 'mic', titleKey: 'hermesLazyDeps.catStt' },
|
||||
{ prefix: 'search.', icon: 'search', titleKey: 'hermesLazyDeps.catSearch' },
|
||||
{ prefix: 'provider.', icon: 'shield', titleKey: 'hermesLazyDeps.catProvider' },
|
||||
{ prefix: 'memory.', icon: 'inbox', titleKey: 'hermesLazyDeps.catMemory' },
|
||||
{ prefix: 'image.', icon: 'image', titleKey: 'hermesLazyDeps.catImage' },
|
||||
]
|
||||
|
||||
const DESC_OVERRIDE_KEY = 'hermesLazyDeps.descOverride' // i18n.key 下的 feature → 描述
|
||||
@@ -28,7 +29,7 @@ const DESC_OVERRIDE_KEY = 'hermesLazyDeps.descOverride' // i18n.key 下的 feat
|
||||
// 把 feature 按分类分组
|
||||
function groupByCategory(features) {
|
||||
const groups = CATEGORIES.map(c => ({ ...c, items: [] }))
|
||||
const other = { prefix: '', emoji: '🧩', titleKey: 'hermesLazyDeps.catOther', items: [] }
|
||||
const other = { prefix: '', icon: 'file', titleKey: 'hermesLazyDeps.catOther', items: [] }
|
||||
for (const f of features) {
|
||||
const cat = groups.find(g => f.feature.startsWith(g.prefix))
|
||||
if (cat) cat.items.push(f)
|
||||
@@ -83,7 +84,7 @@ async function loadAndRender(page) {
|
||||
const features = featuresResp.features || []
|
||||
if (!features.length) {
|
||||
content.innerHTML = `<div class="empty-state empty-compact">
|
||||
<div class="empty-icon">📦</div>
|
||||
<div class="empty-icon">${svgIcon('inbox', { size: 32 })}</div>
|
||||
<div class="empty-title">${escapeHtml(t('hermesLazyDeps.emptyTitle'))}</div>
|
||||
</div>`
|
||||
return
|
||||
@@ -113,7 +114,7 @@ function renderGroup(group, status) {
|
||||
return `
|
||||
<div class="config-section">
|
||||
<div class="config-section-title">
|
||||
<span style="font-size:18px;line-height:1">${group.emoji}</span>
|
||||
<span style="display:inline-flex;align-items:center;color:var(--accent);margin-right:8px">${svgIcon(group.icon, { size: 18 })}</span>
|
||||
${escapeHtml(t(group.titleKey))}
|
||||
</div>
|
||||
<div class="lazy-deps-grid">
|
||||
@@ -130,7 +131,7 @@ function renderItem(f, st) {
|
||||
const specsTitle = (f.specs || []).join('\n')
|
||||
const featureLabel = featureDisplayName(f.feature)
|
||||
const stateBadge = satisfied
|
||||
? `<span class="lazy-deps-badge ok">✓ ${escapeHtml(t('hermesLazyDeps.installed'))}</span>`
|
||||
? `<span class="lazy-deps-badge ok">${svgIcon('check', { size: 11 })} ${escapeHtml(t('hermesLazyDeps.installed'))}</span>`
|
||||
: (known
|
||||
? `<span class="lazy-deps-badge warn">${escapeHtml(t('hermesLazyDeps.notInstalled'))}</span>`
|
||||
: `<span class="lazy-deps-badge unknown">?</span>`)
|
||||
|
||||
@@ -117,7 +117,7 @@ export function render() {
|
||||
levelFilter !== 'ALL' ? levelFilter : null,
|
||||
)
|
||||
} catch (e) {
|
||||
entries = [{ raw: `⚠️ ${t('engine.logsLoadFailed')}: ${e.message || e}` }]
|
||||
entries = [{ raw: `[ERROR] ${t('engine.logsLoadFailed')}: ${e.message || e}`, level: 'ERROR' }]
|
||||
}
|
||||
loading = false
|
||||
draw()
|
||||
|
||||
@@ -15,6 +15,7 @@ import { api } from '../../../lib/tauri-api.js'
|
||||
import { toast } from '../../../components/toast.js'
|
||||
import { showModal, showContentModal } from '../../../components/modal.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
const OAUTH_BASE = '/api/providers/oauth'
|
||||
|
||||
@@ -27,7 +28,7 @@ function renderInlineError(err) {
|
||||
const h = humanizeError(err, t('engine.hermesOAuthTitle'))
|
||||
return `
|
||||
<div class="page-inline-error">
|
||||
<div class="page-inline-error-icon">⚠️</div>
|
||||
<div class="page-inline-error-icon">${svgIcon('alert-triangle', { size: 20 })}</div>
|
||||
<div class="page-inline-error-body">
|
||||
<div class="page-inline-error-message">${escHtml(h.message)}</div>
|
||||
${h.hint ? `<div class="page-inline-error-hint">${escHtml(h.hint)}</div>` : ''}
|
||||
@@ -62,7 +63,7 @@ export function render() {
|
||||
${error ? renderInlineError(error) : ''}
|
||||
${(!loading && !error && !providers.length) ? `
|
||||
<div class="empty-state empty-compact">
|
||||
<div class="empty-icon">🔐</div>
|
||||
<div class="empty-icon">${svgIcon('lock', { size: 32 })}</div>
|
||||
<div class="empty-title">${escHtml(t('engine.hermesOAuthEmpty'))}</div>
|
||||
</div>` : ''}
|
||||
${(!loading && providers.length) ? `
|
||||
|
||||
@@ -16,6 +16,7 @@ import { toast } from '../../../components/toast.js'
|
||||
import { showConfirm, showModal } from '../../../components/modal.js'
|
||||
import { humanizeError } from '../../../lib/humanize-error.js'
|
||||
import { getChatStore } from '../lib/chat-store.js'
|
||||
import { svgIcon } from '../lib/svg-icons.js'
|
||||
|
||||
const chatStore = getChatStore()
|
||||
|
||||
@@ -28,7 +29,7 @@ function renderInlineError(err) {
|
||||
const h = humanizeError(err, t('engine.hermesProfilesTitle'))
|
||||
return `
|
||||
<div class="page-inline-error">
|
||||
<div class="page-inline-error-icon">⚠️</div>
|
||||
<div class="page-inline-error-icon">${svgIcon('alert-triangle', { size: 20 })}</div>
|
||||
<div class="page-inline-error-body">
|
||||
<div class="page-inline-error-message">${escHtml(h.message)}</div>
|
||||
${h.hint ? `<div class="page-inline-error-hint">${escHtml(h.hint)}</div>` : ''}
|
||||
@@ -64,7 +65,7 @@ export function render() {
|
||||
${error ? renderInlineError(error) : ''}
|
||||
${(!loading && !error && !profiles.length) ? `
|
||||
<div class="empty-state empty-compact">
|
||||
<div class="empty-icon">📁</div>
|
||||
<div class="empty-icon">${svgIcon('folder', { size: 32 })}</div>
|
||||
<div class="empty-title">${escHtml(t('engine.hermesProfilesEmpty'))}</div>
|
||||
</div>` : ''}
|
||||
${(!loading && profiles.length) ? `
|
||||
|
||||
@@ -438,6 +438,14 @@ mark {
|
||||
margin-bottom: var(--space-md);
|
||||
opacity: 0.85;
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.empty-state .empty-icon svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.empty-state .empty-title {
|
||||
|
||||
@@ -2675,6 +2675,14 @@
|
||||
line-height: 1;
|
||||
margin-top: 2px;
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--error, #ef4444);
|
||||
}
|
||||
.page-inline-error-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.page-inline-error-body {
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user