From 074fb58329c0ca704e2097b20566ce9b9419967d Mon Sep 17 00:00:00 2001 From: "lisheng.lisheng" Date: Mon, 17 Aug 2026 00:20:29 +0800 Subject: [PATCH] feat(dsh): welcome cards send a query into the conversation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking a welcome card used to POST /bailian/console and render the raw feature result inline — a dead end: the user saw a summary with no way to follow up, and the agent never learned the question was asked. Now each feature declares a `query` (natural-language phrasing) and the card drops it into the conversation input, then submits. The agent routes it to the matching tool itself, can AskUserQuestion for missing params, and the whole exchange stays in the transcript where the user can ask follow-ups. This removes the inline result panel and its loading state. Drop the `apikey` feature: it only echoed a masked key, which the settings page already shows. `bl token-plan personal-key` stays as a CLI command. Co-Authored-By: Claude --- packages/dsh/src/client.ts | 69 +++++++++++++----------------------- packages/dsh/src/features.ts | 19 +++------- 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/packages/dsh/src/client.ts b/packages/dsh/src/client.ts index 56b0d91..54158db 100644 --- a/packages/dsh/src/client.ts +++ b/packages/dsh/src/client.ts @@ -28,8 +28,8 @@ function insertStyles(css: string): () => void { }; } -/** Services required by the client plugin. */ -export const inject = ["slots"]; +/** Services required by the client plugin (conversation for the input hub to send queries). */ +export const inject = ["slots", "sessions", "conversation"]; /** Browser Cordis plugin: registers the "Bailian" settings.section page. */ export function apply(ctx: any): void { @@ -912,6 +912,26 @@ export function apply(ctx: any): void { .bw-card-desc{font-size:13px;color:var(--dsw-text-secondary,#71717a);line-height:1.6} `); + // Help the user send a natural-language query into the conversation (the + // agent then routes it to the right tool / may AskUserQuestion for params). + const sessionsSvc = ctx.get("sessions"); + const inputHub = ctx.conversation && ctx.conversation.input ? ctx.conversation.input : undefined; + function sendQuery(sessionId: string, text: string): boolean { + try { + if (!inputHub) return false; + let shell: any; + const actx = sessionsSvc && sessionsSvc.scope ? sessionsSvc.scope(sessionId) : undefined; + if (actx && inputHub.for) shell = inputHub.for(actx); + else if (inputHub.shell) shell = inputHub.shell(sessionId); + if (!shell) return false; + shell.setDraft(text); + if (shell.submit) shell.submit(); + return true; + } catch { + return false; + } + } + slots.inject("conversation.input.dock", () => slots.register( { name: "conversation.input.dock", id: "bailian-welcome", order: -100 }, @@ -919,24 +939,9 @@ export function apply(ctx: any): void { const session = props && props.session; if (!session || session.blank !== true) return null; const [tab, setTab] = React.useState("rec"); - const [feat, setFeat] = React.useState(null); - const [featLoading, setFeatLoading] = React.useState(""); let active = WELCOME_TABS.find((t) => t.id === tab); if (!active) active = WELCOME_TABS[0]; - - function runFeature(id: string) { - setFeatLoading(id); - setFeat(null); - fetch("/bailian/console", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ featureId: id }), - }) - .then((r) => r.json()) - .then((d) => setFeat(d)) - .catch((e) => setFeat({ error: e && e.message ? e.message : String(e) })) - .then(() => setFeatLoading("")); - } + const sessionId = session.sessionId; return React.createElement( "div", { className: "bw-wrap" }, @@ -980,7 +985,7 @@ export function apply(ctx: any): void { { key: c.title, className: "bw-card", - onClick: feature ? () => runFeature(feature.id) : undefined, + onClick: feature ? () => sendQuery(sessionId, feature.query) : undefined, style: feature ? undefined : { cursor: "default", opacity: 0.75 }, }, React.createElement("div", { className: "bw-card-title" }, c.title), @@ -989,36 +994,12 @@ export function apply(ctx: any): void { ? React.createElement( "div", { style: { marginTop: "8px", fontSize: "12px", color: "#6366f1" } }, - featLoading === feature.id ? "查询中..." : "点击查询 →", + "点击提问 →", ) : null, ); }), ), - feat - ? React.createElement( - "div", - { className: "bw-card", style: { marginTop: "14px" } }, - feat.error - ? React.createElement( - "div", - { className: "bw-card-desc", style: { color: "#e5484d" } }, - feat.error, - ) - : React.createElement( - "pre", - { - style: { - whiteSpace: "pre-wrap", - fontSize: "13px", - margin: 0, - color: "var(--dsw-text,#18181b)", - }, - }, - feat.summary || JSON.stringify(feat.data, null, 2), - ), - ) - : null, ); }, ), diff --git a/packages/dsh/src/features.ts b/packages/dsh/src/features.ts index 81544bb..2a96871 100644 --- a/packages/dsh/src/features.ts +++ b/packages/dsh/src/features.ts @@ -28,6 +28,8 @@ export interface BailianFeature { desc: string; /** Tool description: tells the LLM which user utterances should use it. */ intent: string; + /** Natural-language query sent into the conversation when the card is clicked. */ + query: string; /** `bl` command args (without `--output`); the Host appends `--output json`. */ argv: string[]; /** Args appended when the user supplies no params (e.g. ["--all"]). */ @@ -64,6 +66,7 @@ export const FEATURES: BailianFeature[] = [ desc: "查询免费额度用量,一键开启「用完即停」,额度耗尽自动停止调用,不再产生意外扣费", intent: "查询百炼免费额度用量与『用完即停』防护状态。当用户提到免费额度、额度耗尽、意外扣费、用完即停、额度防护时使用。", + query: "帮我查看百炼免费额度用量,并告诉我怎么开启「用完即停」防护", argv: ["usage", "freetier"], defaultArgs: ["--all"], paramFlags: [ @@ -99,6 +102,7 @@ export const FEATURES: BailianFeature[] = [ desc: "各模型/TokenPlan 的用量与百分比一次查清,自动生成用量分析", intent: "查询百炼 TokenPlan 个人版用量(5 小时/1 周窗口百分比、重置时间、套餐、用量包)。当用户问用量、用了多少、额度百分比、TokenPlan 使用情况时使用。", + query: "帮我查询百炼 TokenPlan 个人版用量(5 小时/1 周窗口、套餐与用量包)", argv: ["token-plan", "personal-usage"], summarize: (d) => { if (!d || typeof d !== "object") return "未获取到用量数据。"; @@ -118,21 +122,6 @@ export const FEATURES: BailianFeature[] = [ : "用量: " + JSON.stringify(d).slice(0, 300); }, }, - { - id: "apikey", - title: "API Key 管理", - desc: "查看 TokenPlan API Key(脱敏),创建/重置可在控制台或 bl 完成", - intent: - "查询当前百炼 TokenPlan API Key(脱敏)。当用户问 API Key、密钥、我的 key、查看 key 时使用。", - argv: ["token-plan", "personal-key"], - summarize: (d) => { - if (!d || typeof d !== "object") return "未获取到 API Key。"; - const mask = pick(d, "mask_apikey", "maskApikey", "apiKey"); - const id = pick(d, "key_id", "keyId"); - if (mask) return `API Key: ${mask}${id !== undefined ? `(id: ${id})` : ""}`; - return "API Key: " + JSON.stringify(d).slice(0, 300); - }, - }, ]; export function featureById(id: string): BailianFeature | undefined {