mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-08 17:21:38 +08:00
feat: prepare v0.18.0 release
This commit is contained in:
7
src/engines/hermes/lib/hermes-run-events.js
Normal file
7
src/engines/hermes/lib/hermes-run-events.js
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 心甜Claw · 产品落地页
|
||||
* ------------------------------------------------------------------
|
||||
* 面向 Windows 桌面客户端的产品宣传 + 下载引导页。
|
||||
* 面向 Windows / macOS / Linux 桌面客户端的产品宣传 + 下载引导页。
|
||||
* 所有可见文本走 i18n(engine.xt*),对外链接统一经过 openExternal()
|
||||
* 在 Tauri 桌面端走 @tauri-apps/plugin-shell,Web 端回退到 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 -->
|
||||
|
||||
@@ -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
|
||||
* ========================================================================== */
|
||||
|
||||
Reference in New Issue
Block a user