feat: prepare v0.18.0 release

This commit is contained in:
晴天
2026-06-06 18:32:37 +08:00
parent f340b64028
commit de1531d111
57 changed files with 1591 additions and 453 deletions

View File

@@ -0,0 +1,7 @@
/**
* Predicate for Hermes `hermes-run-*` Tauri events.
* Requires a known run_id so concurrent runs cannot leak output across listeners.
*/
export function matchesHermesRun(runId, eventRunId) {
return runId != null && eventRunId === runId
}

View File

@@ -13,6 +13,7 @@
*/
import { t } from '../../../lib/i18n.js'
import { api, isTauriRuntime, safeTauriListen } from '../../../lib/tauri-api.js'
import { matchesHermesRun } from '../lib/hermes-run-events.js'
import { svgIcon } from '../lib/svg-icons.js'
/**
@@ -58,27 +59,27 @@ async function runHermesAgentAndWaitFinal(input) {
cleanup()
reject(err)
}
const matchesRun = (rid) => !runId || !rid || rid === runId
;(async () => {
try {
unsubs.push(await safeTauriListen('hermes-run-started', (e) => {
if (!runId && e?.payload?.run_id) runId = e.payload.run_id
const rid = e?.payload?.run_id
if (!runId && rid) runId = rid
}))
unsubs.push(await safeTauriListen('hermes-run-delta', (e) => {
if (!matchesRun(e?.payload?.run_id)) return
if (!matchesHermesRun(runId, e?.payload?.run_id)) return
accumulated += e?.payload?.delta || ''
}))
unsubs.push(await safeTauriListen('hermes-run-done', (e) => {
if (!matchesRun(e?.payload?.run_id)) return
if (!matchesHermesRun(runId, e?.payload?.run_id)) return
const out = (e?.payload?.output || accumulated || '').trim()
finish(out)
}))
unsubs.push(await safeTauriListen('hermes-run-error', (e) => {
if (!matchesRun(e?.payload?.run_id)) return
if (!matchesHermesRun(runId, e?.payload?.run_id)) return
fail(new Error(e?.payload?.error || 'unknown error'))
}))
unsubs.push(await safeTauriListen('hermes-run-cancelled', (e) => {
if (!matchesRun(e?.payload?.run_id)) return
if (!matchesHermesRun(runId, e?.payload?.run_id)) return
finish(accumulated.trim() || '(cancelled)')
}))
@@ -89,7 +90,7 @@ async function runHermesAgentAndWaitFinal(input) {
// 防御:如果 done 事件因为顺序问题尚未派发(理论上不会发生),等一拍兜底
setTimeout(() => {
if (!settled) finish(accumulated.trim())
if (!settled && runId) finish(accumulated.trim())
}, 300)
} catch (e) {
fail(e)
@@ -303,11 +304,13 @@ export function render() {
// 每个 profile run 完后切到下一个。
// 这是个 trade-off — 真正的并发需要后端改造支持 per-call profile。
let activeProfile = null
let initialProfile = null
try {
// 记下当前 active profile 用于最后还原
const curResp = await api.hermesProfilesList().catch(() => null)
const curArr = Array.isArray(curResp) ? curResp : (curResp?.profiles || [])
activeProfile = curResp?.active || curArr.find(p => p.active)?.name || 'default'
initialProfile = curResp?.active || curArr.find(p => p.active)?.name || 'default'
activeProfile = initialProfile
} catch {}
for (let i = 0; i < targets.length; i++) {
@@ -333,6 +336,11 @@ export function render() {
}
// 还原 active profile如果改了— 静默尝试
if (initialProfile && activeProfile !== initialProfile) {
try {
await api.hermesProfileUse(initialProfile)
} catch {}
}
sending = false
draw()
}

View File

@@ -2,7 +2,7 @@
* 心甜Claw 引擎(产品宣传入口)
* ------------------------------------------------------------------
* 这不是一个本地引擎而是「心甜Claw」产品的一个产品落地页入口
* - 桌面客户端 + SaaS 后端Windows 安装即用
* - 桌面客户端 + SaaS 后端Windows / macOS / Linux 安装即用
* - ClawPanel 里只承载宣传 + 跳转下载
*
* 因此它的 detect/boot/cleanup 都是 no-op永远 ready
@@ -19,7 +19,7 @@ let _listeners = []
export default {
id: 'xintian',
name: '心甜Claw',
description: 'Xintian Claw · Worry-free AI Companion for Windows',
description: 'Xintian Claw · Worry-free AI Companion for desktop',
icon: XINTIAN_ICON,
async detect() {

View File

@@ -1,7 +1,7 @@
/**
* 心甜Claw · 产品落地页
* ------------------------------------------------------------------
* 面向 Windows 桌面客户端的产品宣传 + 下载引导页。
* 面向 Windows / macOS / Linux 桌面客户端的产品宣传 + 下载引导页。
* 所有可见文本走 i18nengine.xt*),对外链接统一经过 openExternal()
* 在 Tauri 桌面端走 @tauri-apps/plugin-shellWeb 端回退到 window.open。
*/
@@ -27,6 +27,10 @@ const ICON = {
channels: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>`,
shield: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`,
windows: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 5.47 10.5 4.45v7.02H3V5.47zM10.5 12.53v7.02L3 18.53v-6zm1.12-8.24L22 3v8.47H11.62V4.29zM22 12.53V21l-10.38-1.3v-7.17H22z"/></svg>`,
monitor: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8"/><path d="M12 16v4"/></svg>`,
token: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8"/><path d="M12 8v8"/><path d="M8 12h8"/></svg>`,
layers: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m19 12 2 1.1-9 5-9-5L5 12"/><path d="m19 16 2 1.1-9 5-9-5L5 16"/></svg>`,
model: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"/><path d="M8 9h8"/><path d="M8 13h5"/><path d="M15.5 15.5 17 17"/><circle cx="14" cy="14" r="2"/></svg>`,
download: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
external: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`,
check: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
@@ -55,9 +59,9 @@ function getFeatures() {
{ icon: ICON.brain, title: t('engine.xtFeatMemoryTitle'), desc: t('engine.xtFeatMemoryDesc') },
{ icon: ICON.book, title: t('engine.xtFeatRagTitle'), desc: t('engine.xtFeatRagDesc') },
{ icon: ICON.clock, title: t('engine.xtFeatCronTitle'), desc: t('engine.xtFeatCronDesc') },
{ icon: ICON.skills, title: t('engine.xtFeatSkillsTitle'), desc: t('engine.xtFeatSkillsDesc') },
{ icon: ICON.model, title: t('engine.xtFeatSkillsTitle'), desc: t('engine.xtFeatSkillsDesc') },
{ icon: ICON.channels, title: t('engine.xtFeatChannelTitle'), desc: t('engine.xtFeatChannelDesc') },
{ icon: ICON.shield, title: t('engine.xtFeatOfflineTitle'), desc: t('engine.xtFeatOfflineDesc') },
{ icon: ICON.token, title: t('engine.xtFeatOfflineTitle'), desc: t('engine.xtFeatOfflineDesc') },
]
}
@@ -89,6 +93,15 @@ function getCompareCards() {
]
}
function getHeroProofs() {
return [
{ icon: ICON.monitor, title: t('engine.xtProofPlatformTitle'), desc: t('engine.xtProofPlatformDesc') },
{ icon: ICON.token, title: t('engine.xtProofTokenTitle'), desc: t('engine.xtProofTokenDesc') },
{ icon: ICON.layers, title: t('engine.xtProofPlanTitle'), desc: t('engine.xtProofPlanDesc') },
{ icon: ICON.model, title: t('engine.xtProofModelTitle'), desc: t('engine.xtProofModelDesc') },
]
}
/** 亮点清单CTA 区下方) */
function getChecklist() {
return [
@@ -135,6 +148,17 @@ export async function render() {
const bullets = getChecklist()
.map(b => `<li class="xt-bullet">${ICON.check}<span>${esc(b)}</span></li>`).join('')
const heroProofs = getHeroProofs()
.map(p => `
<div class="xt-proof">
<span class="xt-proof-ico">${p.icon}</span>
<span class="xt-proof-copy">
<strong>${esc(p.title)}</strong>
<span>${esc(p.desc)}</span>
</span>
</div>
`).join('')
root.innerHTML = `
<div class="xt-stage">
<!-- Decorative aurora background -->
@@ -158,7 +182,7 @@ export async function render() {
<p class="xt-hero-sub">${esc(t('engine.xtHeroSub'))}</p>
<div class="xt-hero-actions">
<button class="xt-btn xt-btn--primary" data-xt-action="download">
${ICON.windows}
${ICON.download}
<span>${esc(t('engine.xtCtaDownloadWin'))}</span>
</button>
<button class="xt-btn xt-btn--ghost" data-xt-action="website">
@@ -173,6 +197,7 @@ export async function render() {
<span class="xt-hero-meta-sep">·</span>
<span class="xt-hero-meta-item">${esc(t('engine.xtHeroFreeTrial'))}</span>
</div>
<div class="xt-hero-proof">${heroProofs}</div>
</section>
<!-- 2 · Features -->

View File

@@ -271,6 +271,74 @@
}
[data-engine="xintian"] .xt-hero-meta-sep { opacity: 0.5; }
[data-engine="xintian"] .xt-hero-proof {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin: 26px auto 0;
max-width: 760px;
}
[data-engine="xintian"] .xt-proof {
display: flex;
align-items: flex-start;
gap: 10px;
min-width: 0;
padding: 12px 13px;
border: 1px solid var(--xt-border);
border-radius: 14px;
background: color-mix(in srgb, var(--xt-surface-1) 82%, transparent);
box-shadow: 0 10px 24px -18px rgba(27, 20, 16, 0.18);
text-align: left;
}
[data-engine="xintian"] .xt-proof-ico {
width: 24px;
height: 24px;
display: inline-grid;
place-items: center;
flex: 0 0 24px;
border-radius: 8px;
background: var(--xt-accent-soft);
color: var(--xt-accent);
}
[data-engine="xintian"] .xt-proof-ico svg {
width: 14px;
height: 14px;
}
[data-engine="xintian"] .xt-proof-copy {
min-width: 0;
display: grid;
gap: 3px;
}
[data-engine="xintian"] .xt-proof-copy strong {
color: var(--xt-ink-primary);
font-size: 12px;
line-height: 1.2;
}
[data-engine="xintian"] .xt-proof-copy span {
color: var(--xt-ink-tertiary);
font-size: 11px;
line-height: 1.35;
}
@media (max-width: 980px) {
[data-engine="xintian"] .xt-hero-proof {
grid-template-columns: repeat(2, minmax(0, 1fr));
max-width: 520px;
}
}
@media (max-width: 520px) {
[data-engine="xintian"] .xt-hero-proof {
grid-template-columns: 1fr;
}
}
/* ==========================================================================
* 4 · Buttons
* ========================================================================== */