fmt(js): npm run fix on merge (#101102)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
hermes-seaeye[bot]
2026-09-02 08:42:10 +00:00
committed by GitHub
parent aa80626764
commit 6840bb02e8
22 changed files with 119 additions and 116 deletions
+8 -8
View File
@@ -1,9 +1,4 @@
import type {
LocalCatalogModel,
LocalHardware,
LocalModelsStatus,
LocalRuntimeJob
} from '@/types/hermes'
import type { LocalCatalogModel, LocalHardware, LocalModelsStatus, LocalRuntimeJob } from '@/types/hermes'
import { hermesApi, profileScoped } from './client'
@@ -147,7 +142,10 @@ export function listHFRepoFiles(repo: string): Promise<{ files: HFFileGroup[] }>
})
}
export function downloadBrowsedModel(repo: string, paths: string[]): Promise<{ already_downloaded?: boolean; job_id: null | string; model_id: string }> {
export function downloadBrowsedModel(
repo: string,
paths: string[]
): Promise<{ already_downloaded?: boolean; job_id: null | string; model_id: string }> {
return hermesApi<{ already_downloaded?: boolean; job_id: null | string; model_id: string }>({
...profileScoped(),
body: { paths, repo },
@@ -156,7 +154,9 @@ export function downloadBrowsedModel(repo: string, paths: string[]): Promise<{ a
})
}
export function sideloadLocalModel(path: string): Promise<{ already_present?: boolean; model_id: string; ok: boolean }> {
export function sideloadLocalModel(
path: string
): Promise<{ already_present?: boolean; model_id: string; ok: boolean }> {
return hermesApi<{ already_present?: boolean; model_id: string; ok: boolean }>({
...profileScoped(),
body: { path },
+3 -1
View File
@@ -286,7 +286,9 @@ function DelegationGroup({ group, nowMs }: { group: RootGroup; nowMs: number })
<section className="grid min-w-0 gap-3">
<p className="text-[0.66rem] font-medium uppercase tracking-wider text-muted-foreground/70">
{group.delegationIndex > 0 ? t.agents.delegation(group.delegationIndex) : ''}
{group.batchTag ? <span className="ml-1 font-mono text-muted-foreground/60">[{group.batchTag}]</span> : null}{' '}
{group.batchTag ? (
<span className="ml-1 font-mono text-muted-foreground/60">[{group.batchTag}]</span>
) : null}{' '}
<span className="text-muted-foreground/50">·</span> {t.agents.workers(group.nodes.length)}
{activeWorkers > 0 ? <span className="text-primary/85"> · {t.agents.workersActive(activeWorkers)}</span> : null}
</p>
@@ -52,8 +52,9 @@ describe('tileOwnerRoute', () => {
)
expect(routed).toEqual({ connectionId: 'pandora', profile: 'work', targetProfile: 'ceo' })
expect(tileOwnerRoute([tile({ ownerRoute: { connectionId: 'p', profile: 'w' }, storedSessionId: 's1' })], [], 's1'))
.not.toHaveProperty('targetProfile')
expect(
tileOwnerRoute([tile({ ownerRoute: { connectionId: 'p', profile: 'w' }, storedSessionId: 's1' })], [], 's1')
).not.toHaveProperty('targetProfile')
})
it('narrows a bare profile owner away', () => {
@@ -23,8 +23,7 @@ export function tileOwnerRoute(
storedSessionId: string
): SessionOwnerRoute | undefined {
const owner: SessionOwnerScope =
tiles.find(tile => tile.storedSessionId === storedSessionId)?.ownerRoute ??
knownSessionOwner(rows, storedSessionId)
tiles.find(tile => tile.storedSessionId === storedSessionId)?.ownerRoute ?? knownSessionOwner(rows, storedSessionId)
if (!owner || typeof owner !== 'object' || !owner.connectionId) {
return undefined
@@ -970,7 +970,9 @@ describe('usePromptActions /compress', () => {
act(() => {
submitted = handle!.submitTextRaw('/compress')
})
await waitFor(() => expect(requestGateway).toHaveBeenCalledWith('session.compress', expect.anything(), SESSION_COMPRESS_TIMEOUT_MS))
await waitFor(() =>
expect(requestGateway).toHaveBeenCalledWith('session.compress', expect.anything(), SESSION_COMPRESS_TIMEOUT_MS)
)
// Switch to session B before compression resolves.
activeSessionIdRef.current = RUNTIME_SESSION_B
@@ -1029,7 +1031,9 @@ describe('usePromptActions /compress', () => {
act(() => {
submitted = handle!.submitTextRaw('/compress')
})
await waitFor(() => expect(requestGateway).toHaveBeenCalledWith('session.compress', expect.anything(), SESSION_COMPRESS_TIMEOUT_MS))
await waitFor(() =>
expect(requestGateway).toHaveBeenCalledWith('session.compress', expect.anything(), SESSION_COMPRESS_TIMEOUT_MS)
)
activeSessionIdRef.current = RUNTIME_SESSION_B
storedSessionIdRef.current = 'stored-b'
rejectCompress(new Error('compression failed'))
@@ -2024,7 +2024,7 @@ export function useSessionActions({
await ensureGatewayProfile(profile)
}
const requestBranchGateway = <T,>(method: string, params: Record<string, unknown>): Promise<T> =>
const requestBranchGateway = <T>(method: string, params: Record<string, unknown>): Promise<T> =>
ownerRoute
? requestGatewayForAgent<T>(ownerRoute.connectionId, ownerRoute.profile, method, params)
: requestGateway<T>(method, params)
@@ -303,11 +303,7 @@ export function useSessionListActions({ profileScope }: UseSessionListActionsArg
// whole list re-renders once per turn/broadcast for nothing.
setSessions(prev => {
const incoming = dropTombstoned(
carryForwardFailedProfileSessions(
prev,
recents.sessions ?? [],
recents.errors ?? result.errors
)
carryForwardFailedProfileSessions(prev, recents.sessions ?? [], recents.errors ?? result.errors)
)
const next = mergeSessionPage(prev, incoming, sessionsToKeep())
@@ -24,7 +24,21 @@ import {
sideloadLocalModel
} from '@/hermes'
import { useI18n } from '@/i18n'
import { Check, CheckCircle2, Cpu, Download, Eject, FolderOpen, Loader2, Monitor, Package, Search, StopFilled, Trash2, Zap } from '@/lib/icons'
import {
Check,
CheckCircle2,
Cpu,
Download,
Eject,
FolderOpen,
Loader2,
Monitor,
Package,
Search,
StopFilled,
Trash2,
Zap
} from '@/lib/icons'
import { cn } from '@/lib/utils'
import {
$localRuntimeJobs,
@@ -251,9 +265,7 @@ export function LocalModelsSettings() {
const navigate = useNavigate()
const seenQuickstarts = useRef(new Set<string>())
const runningQuickstart = jobs.find(
j => j.kind === 'quickstart' && j.status === 'running'
)
const runningQuickstart = jobs.find(j => j.kind === 'quickstart' && j.status === 'running')
useEffect(() => {
// Event detection, not value mirroring: the ref only remembers which
@@ -298,11 +310,7 @@ export function LocalModelsSettings() {
// Stage rail derived from the job phase: engine -> model -> finish.
const phase = qJob?.phase ?? ''
const stageIndex = ['starting-server', 'setting-default'].includes(phase)
? 2
: phase === 'downloading'
? 1
: 0
const stageIndex = ['starting-server', 'setting-default'].includes(phase) ? 2 : phase === 'downloading' ? 1 : 0
const stages = [copy.quickstartStageEngine, copy.quickstartStageModel, copy.quickstartStageFinish]
@@ -334,9 +342,7 @@ export function LocalModelsSettings() {
{qJob ? (
<>
<p className="mt-2 min-h-10 text-[0.8rem] leading-5 text-muted-foreground">
{liveDetail}
</p>
<p className="mt-2 min-h-10 text-[0.8rem] leading-5 text-muted-foreground">{liveDetail}</p>
<div className="mt-5">
<ProgressBar percent={qJob.percent} />
@@ -397,8 +403,7 @@ export function LocalModelsSettings() {
// Up to date = the authority (status) says the configured tag is what's
// serving. Shown whenever true — not only right after an update.
const updateApplied =
status.runtime_installed && !status.update_available && status.tag === status.configured_tag
const updateApplied = status.runtime_installed && !status.update_available && status.tag === status.configured_tag
return (
<SettingsContent>
@@ -511,9 +516,7 @@ export function LocalModelsSettings() {
/>
)}
{lastError?.kind === 'runtime-install' && (
<p className="text-[0.75rem] text-destructive">{lastError.error}</p>
)}
{lastError?.kind === 'runtime-install' && <p className="text-[0.75rem] text-destructive">{lastError.error}</p>}
</SettingsSection>
{/* ── This machine ── */}
@@ -571,13 +574,7 @@ export function LocalModelsSettings() {
model.downloaded ? (
<div className="flex items-center justify-end gap-2">
{isLoaded && livePlacement && (
<Tip
label={
livePlacement.spilled
? copy.placementSpilledTip
: copy.placementResidentTip
}
>
<Tip label={livePlacement.spilled ? copy.placementSpilledTip : copy.placementResidentTip}>
<Pill tone={livePlacement.spilled ? 'warn' : 'success'}>
<Cpu className="mr-1 size-3" />
{livePlacement.granted_window_label ?? livePlacement.window_label ?? ''}
@@ -701,8 +698,9 @@ export function LocalModelsSettings() {
model, so a spilled full window goes gray. Anything
starting below its native window gets one quiet
'Up to' pill instead of a start/grow pair. */}
{model.fits && model.start_window_label && (
model.start_window && model.start_window >= model.native_context ? (
{model.fits &&
model.start_window_label &&
(model.start_window && model.start_window >= model.native_context ? (
<Tip label={copy.pillFullContextTip}>
<Pill tone={model.spilled ? 'muted' : 'success'}>
{copy.pillFullContext(model.native_context_label)}
@@ -712,12 +710,9 @@ export function LocalModelsSettings() {
<Tip label={copy.pillGrowsTip}>
<Pill>{copy.pillUpTo(model.native_context_label)}</Pill>
</Tip>
)
)}
))}
{!model.fits && (
<Pill>{copy.pillUpTo(model.native_context_label)}</Pill>
)}
{!model.fits && <Pill>{copy.pillUpTo(model.native_context_label)}</Pill>}
{model.vision && <Pill>{copy.pillVision}</Pill>}
</span>
@@ -758,9 +753,7 @@ export function LocalModelsSettings() {
const isLoadingNow = residency === 'loading'
const livePlacement = status.placement?.[m.id]
const aJob = jobs.find(
j => j.kind === 'model-activate' && j.status === 'running' && j.model_id === m.id
)
const aJob = jobs.find(j => j.kind === 'model-activate' && j.status === 'running' && j.model_id === m.id)
const anyActivateRunning = jobs.some(j => j.kind === 'model-activate' && j.status === 'running')
@@ -769,9 +762,7 @@ export function LocalModelsSettings() {
action={
<div className="flex items-center justify-end gap-2">
{isLoaded && livePlacement && (
<Tip
label={livePlacement.spilled ? copy.placementSpilledTip : copy.placementResidentTip}
>
<Tip label={livePlacement.spilled ? copy.placementSpilledTip : copy.placementResidentTip}>
<Pill tone={livePlacement.spilled ? 'warn' : 'success'}>
<Cpu className="mr-1 size-3" />
{livePlacement.granted_window_label ?? livePlacement.window_label ?? ''}
@@ -840,9 +831,7 @@ export function LocalModelsSettings() {
})}
</div>
{lastError?.kind === 'model-download' && (
<p className="text-[0.75rem] text-destructive">{lastError.error}</p>
)}
{lastError?.kind === 'model-download' && <p className="text-[0.75rem] text-destructive">{lastError.error}</p>}
</SettingsSection>
<BrowseSection onChanged={refresh} />
@@ -1099,7 +1088,9 @@ function BrowseSection({ onChanged }: { onChanged: () => void }) {
: copy.browseFitUnknown}
</Pill>
<span className="shrink-0 text-[0.7rem] text-muted-foreground">{gbLabel(group.total_bytes)}</span>
<span className="shrink-0 text-[0.7rem] text-muted-foreground">
{gbLabel(group.total_bytes)}
</span>
</span>
)}
</div>
@@ -505,7 +505,8 @@ export function ModelCatalogMenu({
// Managed local model loading into memory right now:
// real load percent, keyed by exact model id (remote
// providers never collide with GGUF stems).
const loadProgress = loadingModels[family.id] ?? (family.fastId ? loadingModels[family.fastId] : undefined)
const loadProgress =
loadingModels[family.id] ?? (family.fastId ? loadingModels[family.fastId] : undefined)
// Effective settings for this row: the live choice when it's
// the active model, otherwise its remembered preset. Row
@@ -602,7 +603,9 @@ export function ModelCatalogMenu({
})}
{!collapsed &&
slug === LOCAL_PROVIDER_SLUG &&
shownDownloads.map(job => <DownloadingModelRow jobId={job.jobId} key={job.jobId} target={job.target} />)}
shownDownloads.map(job => (
<DownloadingModelRow jobId={job.jobId} key={job.jobId} target={job.target} />
))}
</DropdownMenuGroup>
)
})}
@@ -680,10 +683,7 @@ function DownloadingModelRow({ jobId, target }: { jobId: string; target: string
const { t } = useI18n()
const copy = t.modelPicker
const percent = useStoreSelector(
$localRuntimeJobs,
jobs => jobs.find(job => job.job_id === jobId)?.percent ?? null
)
const percent = useStoreSelector($localRuntimeJobs, jobs => jobs.find(job => job.job_id === jobId)?.percent ?? null)
return (
<DropdownMenuItem
@@ -103,7 +103,8 @@ export function useSystemResourcesStatusbarItem(): StatusbarItem {
: null
const ramUsed = hardware ? hardware.ram_total_bytes - hardware.ram_available_bytes : null
const ramPercent = hardware?.ram_total_bytes && ramUsed != null ? Math.round((ramUsed / hardware.ram_total_bytes) * 100) : null
const ramPercent =
hardware?.ram_total_bytes && ramUsed != null ? Math.round((ramUsed / hardware.ram_total_bytes) * 100) : null
// Compact bar label: the numbers a local-inference user glances at.
// "GPU 34% · 18G/32G" with a GPU; "RAM 41G/256G" without.
@@ -124,10 +125,7 @@ export function useSystemResourcesStatusbarItem(): StatusbarItem {
menuAlign: 'end',
menuClassName: 'w-64 p-0',
menuContent: (
<div
className="grid grid-cols-[minmax(0,1fr)] gap-3 p-3 text-[0.75rem]"
data-slot="system-resources-panel"
>
<div className="grid grid-cols-[minmax(0,1fr)] gap-3 p-3 text-[0.75rem]" data-slot="system-resources-panel">
{/* min-w-0 everywhere a flex/grid child must shrink: grid items
default min-width:auto, so a long GPU name's nowrap min-content
props the track open past the w-64 box and overflow-x:hidden
@@ -62,7 +62,7 @@ const HintText: FC<{ children: ReactNode }> = ({ children }) => (
* call (title generation autoloads the same model), never gets a frame,
* and the load looked like nothing was happening. The status route reads
* the same SSE snapshot, so this bar carries the identical percent. */
function useLocalModelLoad(active: boolean): LocalModelLoadProgress & { model: string } | null {
function useLocalModelLoad(active: boolean): (LocalModelLoadProgress & { model: string }) | null {
const model = useStore($currentModel)
const [progress, setProgress] = useState<(LocalModelLoadProgress & { model: string }) | null>(null)
+3 -12
View File
@@ -347,9 +347,7 @@ function ModelResults({
style={{ width: `${Math.max(2, loadProgress.percent)}%` }}
/>
</span>
<span className="text-[0.62rem] tabular-nums text-muted-foreground">
{loadProgress.percent}%
</span>
<span className="text-[0.62rem] tabular-nums text-muted-foreground">{loadProgress.percent}%</span>
</span>
)}
{locked && (
@@ -393,17 +391,10 @@ function DownloadingModelRow({ jobId, target }: { jobId: string; target: string
const { t } = useI18n()
const copy = t.modelPicker
const percent = useStoreSelector(
$localRuntimeJobs,
jobs => jobs.find(job => job.job_id === jobId)?.percent ?? null
)
const percent = useStoreSelector($localRuntimeJobs, jobs => jobs.find(job => job.job_id === jobId)?.percent ?? null)
return (
<CommandItem
className="flex items-center gap-2 pl-6 font-mono opacity-60"
disabled
value={`downloading:${jobId}`}
>
<CommandItem className="flex items-center gap-2 pl-6 font-mono opacity-60" disabled value={`downloading:${jobId}`}>
<span className="min-w-0 flex-1 truncate">{target}</span>
<span className="flex shrink-0 items-center gap-1.5" title={copy.downloading}>
<span className="h-1 w-16 overflow-hidden rounded-full bg-(--ui-bg-tertiary)">
@@ -100,7 +100,9 @@ export function FireworksProviderRow({ onClick }: { onClick: () => void }) {
export function LocalModelsProviderRow({ onClick }: { onClick: () => void }) {
const { t } = useI18n()
return <KeyProviderRow onClick={onClick} pitch={t.onboarding.localModelsPitch} title={t.onboarding.localModelsTitle} />
return (
<KeyProviderRow onClick={onClick} pitch={t.onboarding.localModelsPitch} title={t.onboarding.localModelsTitle} />
)
}
export function OpenRouterProviderRow({ onClick }: { onClick: () => void }) {
+1 -3
View File
@@ -296,9 +296,7 @@ describe('Hermes REST helpers', () => {
api.mockImplementation(({ path }: { path: string }) => {
if (path.startsWith('/api/profiles/sessions/sidebar')) {
return Promise.reject(
new Error('404: {"detail":"No such API endpoint: /api/profiles/sessions/sidebar"}')
)
return Promise.reject(new Error('404: {"detail":"No such API endpoint: /api/profiles/sessions/sidebar"}'))
}
if (path.includes('source=cron')) {
+9 -6
View File
@@ -1153,8 +1153,7 @@ export const en: Translations = {
'A higher-quality model fits this machine but would respond too slowly on its memory bandwidth — this is the best model that stays fast.',
'fastest-resident':
'No model reaches full speed on this hardware; this one comes closest while running entirely in GPU memory.',
'least-painful-spilled':
'No model fits entirely in GPU memory here — this one runs best from system RAM.'
'least-painful-spilled': 'No model fits entirely in GPU memory here — this one runs best from system RAM.'
} as Record<string, string>,
downloaded: 'Downloaded',
downloadAction: size => `Download · ${size}`,
@@ -1164,7 +1163,8 @@ export const en: Translations = {
quickstartTitle: 'Run a model on this machine',
quickstartDetail: (model, size) =>
`One click sets everything up: the local engine, ${model} (${size} download), and your default for new chats. Nothing leaves this computer.`,
quickstartDetailReady: model => `One click makes ${model} your default for new chats. Everything runs on this machine.`,
quickstartDetailReady: model =>
`One click makes ${model} your default for new chats. Everything runs on this machine.`,
quickstartAction: 'Set up for me',
quickstartConfigure: 'Configure…',
quickstartDoneToast: model => `${model} is set up — new chats run on this machine.`,
@@ -1175,7 +1175,8 @@ export const en: Translations = {
useAction: 'Use',
activePill: 'Default',
updateTitle: 'Engine update available',
updateDetail: (next, current) => `A newer llama.cpp build (${next}) is ready to install — you're on ${current}. Models keep working during the download.`,
updateDetail: (next, current) =>
`A newer llama.cpp build (${next}) is ready to install — you're on ${current}. Models keep working during the download.`,
updateAction: 'Update engine',
updating: 'Updating engine…',
upToDateTitle: 'Engine up to date',
@@ -1195,7 +1196,8 @@ export const en: Translations = {
ejectFailed: 'Could not unload the model',
stopServer: 'Turn off',
startServer: 'Turn on',
runtimeRunningDetail: 'The local server is running. Turning it off frees all GPU memory and stops new chats from using local models until you turn it back on.',
runtimeRunningDetail:
'The local server is running. Turning it off frees all GPU memory and stops new chats from using local models until you turn it back on.',
serverStopped: 'Local server stopped — GPU memory freed.',
serverStarted: 'Local server running.',
serverStopFailed: 'Could not stop the local server',
@@ -1208,7 +1210,8 @@ export const en: Translations = {
pillUsesRam: 'Uses system RAM',
pillTooBig: 'Too big for this machine',
browseTitle: 'Find more models',
browseHint: 'Search all of Hugging Face. Models you download here are sized to your machine automatically, but not tested by us.',
browseHint:
'Search all of Hugging Face. Models you download here are sized to your machine automatically, but not tested by us.',
browsePlaceholder: 'Search models by name or author…',
browseSearching: 'Searching Hugging Face',
browseListing: 'Reading model files',
+10 -5
View File
@@ -1067,26 +1067,30 @@ export const ja = defineLocale({
useAction: '使用する',
activePill: 'デフォルト',
updateTitle: 'エンジンの更新があります',
updateDetail: (next, current) => `新しい llama.cpp ビルド(${next})をインストールできます——現在は ${current} です。ダウンロード中もモデルは引き続き使えます。`,
updateDetail: (next, current) =>
`新しい llama.cpp ビルド(${next})をインストールできます——現在は ${current} です。ダウンロード中もモデルは引き続き使えます。`,
updateAction: 'エンジンを更新',
updating: 'エンジンを更新中…',
upToDateTitle: 'エンジンは最新です',
upToDateDetail: (tag, backend) => `llama.cpp ${tag}(${backend})で動作中——Hermes が提供する最新ビルドです。`,
updateToast: next => `ローカルエンジンの新しいビルド(${next})があります。設定 → ローカルモデル から更新できます。`,
updateToast: next =>
`ローカルエンジンの新しいビルド(${next})があります。設定 → ローカルモデル から更新できます。`,
activeDetail: '新しいチャットはこのモデルを使用——最初のメッセージ送信時に読み込みます',
activeNotLoaded: '最初のメッセージで読み込みます',
loadedPill: '読み込み済み',
placementResident: 'すべて GPU 上',
placementSpilled: '一部 RAM 上',
placementResidentTip: 'このコンテキストウィンドウで GPU メモリ内で完全に動作しています — フルスピード。',
placementSpilledTip: 'モデルの一部がシステム RAM から動作しています — 動作しますが遅くなります。よりコンパクトなビルドか小さいコンテキストなら完全に収まります。',
placementSpilledTip:
'モデルの一部がシステム RAM から動作しています — 動作しますが遅くなります。よりコンパクトなビルドか小さいコンテキストなら完全に収まります。',
loadingPill: '読み込み中…',
ejectTip: 'GPU メモリを解放(必要時に再読み込み)',
ejected: 'モデルをアンロードしました——GPU メモリを解放しました。',
ejectFailed: 'モデルをアンロードできませんでした',
stopServer: 'オフにする',
startServer: 'オンにする',
runtimeRunningDetail: 'ローカルサーバーが実行中です。オフにすると GPU メモリを全て解放し、再度オンにするまで新しいチャットはローカルモデルを使用しません。',
runtimeRunningDetail:
'ローカルサーバーが実行中です。オフにすると GPU メモリを全て解放し、再度オンにするまで新しいチャットはローカルモデルを使用しません。',
serverStopped: 'ローカルサーバーを停止しました——GPU メモリを解放しました。',
serverStarted: 'ローカルサーバー実行中。',
serverStopFailed: 'ローカルサーバーを停止できませんでした',
@@ -1099,7 +1103,8 @@ export const ja = defineLocale({
pillUsesRam: 'システム RAM を使用',
pillTooBig: 'このマシンには大きすぎます',
browseTitle: 'さらにモデルを探す',
browseHint: 'Hugging Face 全体を検索できます。ここでダウンロードしたモデルは自動でマシンに合わせて動作しますが、当方でのテストは行われていません。',
browseHint:
'Hugging Face 全体を検索できます。ここでダウンロードしたモデルは自動でマシンに合わせて動作しますが、当方でのテストは行われていません。',
browsePlaceholder: 'モデル名または作者で検索…',
browseSearching: 'Hugging Face を検索中',
browseListing: 'モデルファイルを読み込み中',
+9 -6
View File
@@ -705,7 +705,8 @@ export const zhHant = defineLocale({
'Hermes 執行環境已更新,但桌面應用程式本身仍是舊建置——在應用程式更新之前,新的介面功能(如 Bot Mode)不會顯示。請執行下方的更新以重新建置應用程式。如果此警告仍未消除,請從最新的桌面安裝程式重新安裝。',
bundleOutOfSyncAction: '取得安裝程式',
bundleSwapPending: '重新啟動以完成更新',
bundleSwapPendingDesc: '更新後的應用程式已安裝完成,只需重新啟動 Hermes 即可載入新版本。聊天記錄和設定不會受到影響。',
bundleSwapPendingDesc:
'更新後的應用程式已安裝完成,只需重新啟動 Hermes 即可載入新版本。聊天記錄和設定不會受到影響。',
bundleSwapPendingAction: '重新啟動 Hermes',
updates: '更新',
checkNow: '立即檢查',
@@ -998,8 +999,7 @@ export const zhHant = defineLocale({
runtimeInstalled: '已安裝 llama.cpp 執行環境',
runtimeInstalledDetail: (tag, backend) => `組建 ${tag},${backend} 後端。Hermes 會為您啟動並管理伺服器。`,
installTitle: '安裝本地執行環境',
installDetail:
'下載 llama.cpp 推理引擎(數百 MB)。下載的模型完全在本機執行——無需帳號,資料不會離開您的電腦。',
installDetail: '下載 llama.cpp 推理引擎(數百 MB)。下載的模型完全在本機執行——無需帳號,資料不會離開您的電腦。',
installAction: '安裝執行環境',
installing: '正在安裝執行環境…',
installFailed: '執行環境安裝失敗',
@@ -1012,7 +1012,8 @@ export const zhHant = defineLocale({
recommended: '推薦',
recommendedReason: {
'best-quality-resident': '在完全駐留 GPU 且保持全速的模型中品質最高。推薦會在品質與該硬體的預計速度之間權衡。',
'speed-gated-quality': '有更高品質的模型可以裝入這台機器,但受記憶體頻寬限制回應會太慢——這是保持流暢的最佳模型。',
'speed-gated-quality':
'有更高品質的模型可以裝入這台機器,但受記憶體頻寬限制回應會太慢——這是保持流暢的最佳模型。',
'fastest-resident': '沒有模型能在該硬體上達到全速;這是完全駐留 GPU 記憶體中最快的一個。',
'least-painful-spilled': '沒有模型能完全裝入 GPU 記憶體——這是從系統記憶體執行表現最好的一個。'
} as Record<string, string>,
@@ -1024,7 +1025,8 @@ export const zhHant = defineLocale({
useAction: '使用',
activePill: '預設',
updateTitle: '引擎有可用更新',
updateDetail: (next, current) => `新的 llama.cpp 組建(${next})可以安裝——目前為 ${current}。下載期間模型仍可正常使用。`,
updateDetail: (next, current) =>
`新的 llama.cpp 組建(${next})可以安裝——目前為 ${current}。下載期間模型仍可正常使用。`,
updateAction: '更新引擎',
updating: '正在更新引擎…',
upToDateTitle: '引擎已是最新',
@@ -1036,7 +1038,8 @@ export const zhHant = defineLocale({
placementResident: '全部在 GPU',
placementSpilled: '部分在記憶體',
placementResidentTip: '完全在 GPU 記憶體中以此上下文視窗執行——全速。',
placementSpilledTip: '模型的一部分從系統記憶體執行——可用但較慢。更緊湊的版本或更小的上下文可以完全放入顯示記憶體。',
placementSpilledTip:
'模型的一部分從系統記憶體執行——可用但較慢。更緊湊的版本或更小的上下文可以完全放入顯示記憶體。',
loadingPill: '載入中…',
ejectTip: '釋放顯示記憶體(需要時重新載入)',
ejected: '模型已卸載——顯示記憶體已釋放。',
+2 -1
View File
@@ -1359,7 +1359,8 @@ export const zh: Translations = {
useAction: '使用',
activePill: '默认',
updateTitle: '引擎有可用更新',
updateDetail: (next, current) => `新的 llama.cpp 构建(${next})可以安装——当前为 ${current}。下载期间模型仍可正常使用。`,
updateDetail: (next, current) =>
`新的 llama.cpp 构建(${next})可以安装——当前为 ${current}。下载期间模型仍可正常使用。`,
updateAction: '更新引擎',
updating: '正在更新引擎…',
upToDateTitle: '引擎已是最新',
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'vitest'
import { currentPickerSelection, displayModelName, formatModelStatusLabel, modelDisplayParts } from './model-status-label'
import {
currentPickerSelection,
displayModelName,
formatModelStatusLabel,
modelDisplayParts
} from './model-status-label'
import { reasoningEffortLabel } from './reasoning-effort'
describe('model-status-label', () => {
+3 -1
View File
@@ -22,7 +22,9 @@ describe('providerWaitText', () => {
describe('parseModelLoadWait', () => {
it('extracts model and percent from a load frame', () => {
expect(
parseModelLoadWait('⏳ loading Qwen3.6-35B-A3B-UD-Q4_K_M into memory — 42% (responses start once the model is loaded)')
parseModelLoadWait(
'⏳ loading Qwen3.6-35B-A3B-UD-Q4_K_M into memory — 42% (responses start once the model is loaded)'
)
).toEqual({ kind: 'load', model: 'Qwen3.6-35B-A3B-UD-Q4_K_M', percent: 42 })
})
+6 -6
View File
@@ -694,9 +694,7 @@ describe('carryForwardFailedProfileSessions', () => {
session({ id: 'week', last_active: 100, profile: 'default', title: 'This week' })
]
const carried = carryForwardFailedProfileSessions(previous, [], [
{ profile: 'default', error: 'disk I/O error' }
])
const carried = carryForwardFailedProfileSessions(previous, [], [{ profile: 'default', error: 'disk I/O error' }])
expect(carried.map(s => s.id)).toEqual(['running', 'yesterday', 'week'])
expect(carried[1]).toBe(previous[1])
@@ -724,9 +722,11 @@ describe('carryForwardFailedProfileSessions', () => {
const incoming = [session({ id: 'home', last_active: 100, profile: 'default' })]
expect(
carryForwardFailedProfileSessions(previous, incoming, [{ profile: 'work' }]).map(s => s.id)
).toEqual(['idle-newer', 'home', 'idle-older'])
expect(carryForwardFailedProfileSessions(previous, incoming, [{ profile: 'work' }]).map(s => s.id)).toEqual([
'idle-newer',
'home',
'idle-older'
])
})
it('treats a missing profile tag on the error as default', () => {
+2
View File
@@ -334,12 +334,14 @@ function SubagentAccordion({
// `[6a66 3/9]` when the gateway tags the batch; `[3/9]` on older gateways.
const batchTag = item.delegationId?.split('_').at(-1)?.slice(0, 4)
const prefix =
item.taskCount > 1
? `[${batchTag ? `${batchTag} ` : ''}${item.index + 1}/${item.taskCount}] `
: batchTag
? `[${batchTag}] `
: ''
const goalLabel = item.goal || `Subagent ${item.index + 1}`
const title = `${prefix}${open ? goalLabel : compactPreview(goalLabel, 60)}`
const summary = compactPreview((item.summary || '').replace(/\s+/g, ' ').trim(), 72)