diff --git a/.github/workflows/plugin-api-v3.yml b/.github/workflows/plugin-api-v3.yml index a5d03fa..c89437e 100644 --- a/.github/workflows/plugin-api-v3.yml +++ b/.github/workflows/plugin-api-v3.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@v4 with: repository: kachofugetsu09/akashic-agent - ref: 0607e546de923e9377b04fb841d01384b1a666c1 + ref: d5e5177092d74de03588d8675f3504db36b7bacb path: .akashic-core - uses: actions/setup-python@v5 with: diff --git a/akashic.plugin.toml b/akashic.plugin.toml index aa820b1..c5e4338 100644 --- a/akashic.plugin.toml +++ b/akashic.plugin.toml @@ -1,6 +1,6 @@ schema_version = 1 name = "proactive_feedback" -version = "3.0.0" +version = "3.0.1" api_version = 3 entrypoint = "plugin.py" diff --git a/dashboard_panel.css b/dashboard_panel.css index 28c0153..a5d81a4 100644 --- a/dashboard_panel.css +++ b/dashboard_panel.css @@ -1,3 +1,22 @@ +.feedback-empty { + padding: 24px 16px; +} + +.feedback-empty__title { + color: var(--ak-color-text-primary); + font-size: 20px; + font-weight: 600; + letter-spacing: -0.025em; + line-height: 1.25; +} + +.feedback-empty__text { + margin-block-start: 8px; + color: var(--ak-color-text-secondary); + font-size: 13px; + line-height: 1.625; +} + .feedback-detail { display: grid; gap: 20px; diff --git a/dashboard_panel.js b/dashboard_panel.js deleted file mode 100644 index 6386f21..0000000 --- a/dashboard_panel.js +++ /dev/null @@ -1,169 +0,0 @@ -// ../proactive_feedback/dashboard_panel.tsx -import { Chip, api } from "@akashic/dashboard-ui"; -import { jsx, jsxs } from "react/jsx-runtime"; -function _score(value) { - return typeof value === "number" ? value.toFixed(3) : "-"; -} -function _shortTs(value) { - const text = String(value || ""); - if (!text) return "-"; - const d = new Date(text); - if (Number.isNaN(d.getTime())) return text; - return `${d.getMonth() + 1}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; -} -function _lag(value) { - if (typeof value !== "number") return "-"; - if (value < 60) return `${value}s`; - if (value < 3600) return `${Math.round(value / 60)}m`; - return `${(value / 3600).toFixed(1)}h`; -} -function _tone(type) { - if (type === "explicit_quote") return "accent"; - if (type === "topic_follow") return "success"; - if (type === "unscored") return "warning"; - if (type === "no_topic_follow") return "muted"; - return "muted"; -} -function _feedbackTypeLabel(value) { - const type = String(value || ""); - if (type === "explicit_quote") return "\u660E\u786E\u5F15\u7528"; - if (type === "topic_follow") return "\u8BDD\u9898\u5EF6\u7EED"; - if (type === "no_topic_follow") return "\u672A\u5EF6\u7EED\u8BDD\u9898"; - if (type === "unscored") return "\u5F85\u8BC4\u5206"; - return type || "-"; -} -function _confidenceLabel(value) { - const confidence = String(value || ""); - if (confidence === "gold") return "\u91D1\u6807"; - if (confidence === "high") return "\u9AD8"; - if (confidence === "medium") return "\u4E2D"; - if (confidence === "low") return "\u4F4E"; - return confidence || "-"; -} -function _escape(value) { - return String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"); -} -function _cellText(value) { - const text = String(value || "").trim(); - return _escape(text || "-"); -} -function _typeCell(value) { - const type = String(value || ""); - const tone = type === "explicit_quote" ? "accent" : type === "topic_follow" ? "success" : type === "unscored" ? "warning" : "muted"; - return `${_escape(_feedbackTypeLabel(type))}`; -} -function _confidenceTone(value) { - const confidence = String(value || ""); - if (confidence === "gold" || confidence === "high") return "success"; - if (confidence === "medium") return "warning"; - return "muted"; -} -function _confidenceCell(value) { - const confidence = String(value || "-"); - return `${_escape(_confidenceLabel(confidence))}`; -} -function FeedbackDetail(props) { - const item = props.item; - if (!item) { - return /* @__PURE__ */ jsxs("div", { className: "detail-empty", children: [ - /* @__PURE__ */ jsx("div", { className: "detail-empty-title", children: "\u53CD\u9988\u8BE6\u60C5" }), - /* @__PURE__ */ jsx("div", { className: "detail-empty-text", children: "\u70B9\u5F00\u4E00\u6761\u8BB0\u5F55\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u7528\u6237\u56DE\u590D\u3001\u547D\u4E2D\u7684 proactive \u548C\u52A9\u624B\u540E\u7EED\u56DE\u590D\u3002" }) - ] }); - } - const type = String(item.feedback_type || ""); - return /* @__PURE__ */ jsxs("main", { className: "feedback-detail", "aria-labelledby": "feedback-detail-title", children: [ - /* @__PURE__ */ jsxs("header", { className: "feedback-detail__header", children: [ - /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("p", { children: "\u4E3B\u52A8\u6D88\u606F\u6548\u679C" }), - /* @__PURE__ */ jsx("h2", { id: "feedback-detail-title", children: "\u7528\u6237\u5982\u4F55\u56DE\u5E94\u8FD9\u6B21\u4E3B\u52A8\u89E6\u8FBE" }), - /* @__PURE__ */ jsx("span", { children: String(item.session_key || "\u672A\u5173\u8054\u4F1A\u8BDD") }) - ] }), - /* @__PURE__ */ jsx(Chip, { tone: _tone(type), children: _feedbackTypeLabel(type) }) - ] }), - /* @__PURE__ */ jsxs("section", { className: "feedback-summary", "aria-label": "\u53CD\u9988\u5224\u65AD\u6458\u8981", children: [ - /* @__PURE__ */ jsx(SummaryMetric, { label: "\u7F6E\u4FE1\u5EA6", value: _confidenceLabel(item.confidence) }), - /* @__PURE__ */ jsx(SummaryMetric, { label: "\u54CD\u5E94\u5EF6\u8FDF", value: _lag(item.lag_seconds) }), - /* @__PURE__ */ jsx(SummaryMetric, { label: "PUA \u5206\u6570", value: _score(item.pua_score) }) - ] }), - /* @__PURE__ */ jsxs("section", { className: "feedback-timeline", "aria-labelledby": "feedback-timeline-title", children: [ - /* @__PURE__ */ jsx("h3", { id: "feedback-timeline-title", children: "\u5BF9\u8BDD\u94FE\u8DEF" }), - /* @__PURE__ */ jsx(TimelineStep, { index: "1", title: "\u4E3B\u52A8\u6D88\u606F", text: String(item.proactive_preview || item.quoted_preview || "") }), - /* @__PURE__ */ jsx(TimelineStep, { index: "2", title: "\u7528\u6237\u53CD\u9988", text: String(item.user_reply_preview || item.user_preview || ""), emphasis: true }), - /* @__PURE__ */ jsx(TimelineStep, { index: "3", title: "\u52A9\u624B\u540E\u7EED", text: String(item.assistant_preview || "") }) - ] }), - /* @__PURE__ */ jsxs("details", { className: "feedback-technical", children: [ - /* @__PURE__ */ jsx("summary", { children: "\u67E5\u770B\u5339\u914D\u4F9D\u636E\u4E0E\u6D88\u606F\u6807\u8BC6" }), - /* @__PURE__ */ jsxs("dl", { children: [ - /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("dt", { children: "\u5339\u914D\u65B9\u5F0F" }), - /* @__PURE__ */ jsx("dd", { children: String(item.matched_by || "-") }) - ] }), - /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("dt", { children: "\u7528\u6237\u6D88\u606F ID" }), - /* @__PURE__ */ jsx("dd", { children: String(item.user_message_id || "-") }) - ] }), - /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("dt", { children: "\u4E3B\u52A8\u6D88\u606F ID" }), - /* @__PURE__ */ jsx("dd", { children: String(item.proactive_message_id || "-") }) - ] }) - ] }) - ] }) - ] }); -} -function SummaryMetric(props) { - return /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("span", { children: props.label }), - /* @__PURE__ */ jsx("strong", { children: props.value }) - ] }); -} -function TimelineStep(props) { - return /* @__PURE__ */ jsxs("div", { className: `feedback-step${props.emphasis ? " is-emphasis" : ""}`, children: [ - /* @__PURE__ */ jsx("span", { className: "feedback-step__index", children: props.index }), - /* @__PURE__ */ jsxs("div", { children: [ - /* @__PURE__ */ jsx("h4", { children: props.title }), - /* @__PURE__ */ jsx("p", { children: props.text || "\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u5185\u5BB9" }) - ] }) - ] }); -} -window.AkashicDashboard.registerPlugin({ - id: "proactive_feedback", - label: "\u4E3B\u52A8\u53CD\u9988", - viewLabel: "\u4E3B\u52A8\u53CD\u9988", - pageSize: 50, - rowKey: "id", - countTitle(total) { - return `\u5171 ${total} \u6761\u53CD\u9988`; - }, - columns: [ - { key: "created_at", label: "\u65F6\u95F4", width: 96, fmt: "mono-time", cellClass: "mono cell-time", rawTitle: true }, - { key: "feedback_type", label: "\u7C7B\u578B", width: 126, renderCell: _typeCell }, - { key: "confidence", label: "\u7F6E\u4FE1\u5EA6", width: 84, renderCell: _confidenceCell }, - { key: "lag_seconds", label: "\u5EF6\u8FDF", width: 68, fmt: "lag", cellClass: "mono cell-metric", align: "right" }, - { key: "user_reply_preview", label: "\u7528\u6237\u56DE\u590D", flex: true, renderCell: _cellText, cellClass: "content-preview", rawTitle: true }, - { key: "proactive_preview", label: "\u547D\u4E2D\u5185\u5BB9", flex: true, renderCell: _cellText, cellClass: "content-preview", rawTitle: true } - ], - async getCount() { - try { - const overview = await api("/api/dashboard/proactive-feedback/overview"); - return overview.total || 0; - } catch { - return null; - } - }, - async fetchPage({ page, pageSize }) { - const params = new URLSearchParams(); - params.set("page", String(page)); - params.set("page_size", String(pageSize)); - const data = await api(`/api/dashboard/proactive-feedback/events?${params.toString()}`); - return { items: data.items || [], total: data.total || 0 }; - }, - async fetchDetail(item) { - return api(`/api/dashboard/proactive-feedback/events/${item.id}`); - }, - Detail: FeedbackDetail, - formatters: { - score: (value) => _score(value), - lag: (value) => _lag(value), - "mono-time": (value) => _shortTs(value) - } -}); diff --git a/dashboard_panel.tsx b/dashboard_panel.tsx index a1ac814..cc4c556 100644 --- a/dashboard_panel.tsx +++ b/dashboard_panel.tsx @@ -1,6 +1,18 @@ -/// import { type ReactElement } from "react"; -import { Chip, api } from "@akashic/dashboard-ui"; +import { createRoot } from "react-dom/client"; +import "./dashboard_panel.css"; +import type { WebHostContextV1, WebUiDisposer } from "@akashic/web-ui-v1"; +import type { WorkbenchDispatch, WorkbenchPanelEntry, WorkbenchUi } from "@akashic/workbench-ui-v2"; + +let dashboardRequest: WebHostContextV1["http"]["request"] | null = null; + +async function api(path: string, init?: RequestInit): Promise { + if (!dashboardRequest) throw new Error("主动反馈工作台面板未激活"); + const response = await dashboardRequest(path, init); + const body = await response.json() as T & { detail?: unknown; message?: unknown }; + if (!response.ok) throw new Error(String(body.detail ?? body.message ?? `HTTP ${response.status}`)); + return body; +} interface Overview { total: number; @@ -73,7 +85,7 @@ function _cellText(value: unknown): string { function _typeCell(value: unknown): string { const type = String(value || ""); const tone = type === "explicit_quote" ? "accent" : type === "topic_follow" ? "success" : type === "unscored" ? "warning" : "muted"; - return `${_escape(_feedbackTypeLabel(type))}`; + return `${_escape(_feedbackTypeLabel(type))}`; } function _confidenceTone(value: unknown): "success" | "warning" | "muted" { @@ -85,13 +97,15 @@ function _confidenceTone(value: unknown): "success" | "warning" | "muted" { function _confidenceCell(value: unknown): string { const confidence = String(value || "-"); - return `${_escape(_confidenceLabel(confidence))}`; + const tone = _confidenceTone(confidence); + return `${_escape(_confidenceLabel(confidence))}`; } -function FeedbackDetail(props: { item: Record | null }): ReactElement { +function FeedbackDetail(props: { item: Record | null; ui: WorkbenchUi }): ReactElement { const item = props.item; + const Chip = props.ui.Chip; if (!item) { - return
反馈详情
点开一条记录后,这里会显示用户回复、命中的 proactive 和助手后续回复。
; + return
反馈详情
点开一条记录后,这里会显示用户回复、命中的 proactive 和助手后续回复。
; } const type = String(item.feedback_type || ""); return ( @@ -146,10 +160,11 @@ function TimelineStep(props: { index: string; title: string; text: string; empha ); } -window.AkashicDashboard.registerPlugin({ - id: "proactive_feedback", +const panel = { + id: "proactive-feedback", label: "主动反馈", viewLabel: "主动反馈", + order: 70, pageSize: 50, rowKey: "id", @@ -166,32 +181,46 @@ window.AkashicDashboard.registerPlugin({ { key: "proactive_preview", label: "命中内容", flex: true, renderCell: _cellText, cellClass: "content-preview", rawTitle: true }, ], - async getCount(): Promise { + async getCount({ signal }: { signal: AbortSignal }): Promise { try { - const overview = await api("/api/dashboard/proactive-feedback/overview"); + const overview = await api("/api/dashboard/proactive-feedback/overview", { signal }); return overview.total || 0; - } catch { + } catch (error) { + if (signal.aborted) throw error; return null; } }, - async fetchPage({ page, pageSize }: { page: number; pageSize: number }) { + async fetchPage({ page, pageSize, signal }: { page: number; pageSize: number; signal: AbortSignal }) { const params = new URLSearchParams(); params.set("page", String(page)); params.set("page_size", String(pageSize)); - const data = await api(`/api/dashboard/proactive-feedback/events?${params.toString()}`); + const data = await api(`/api/dashboard/proactive-feedback/events?${params.toString()}`, { signal }); return { items: data.items || [], total: data.total || 0 }; }, - async fetchDetail(item: Record) { - return api>(`/api/dashboard/proactive-feedback/events/${item.id}`); + async fetchDetail(item: Record, { signal }: { signal: AbortSignal }) { + return api>(`/api/dashboard/proactive-feedback/events/${item.id}`, { signal }); }, - Detail: FeedbackDetail, + renderDetail(item: Record | null, container: HTMLElement, dispatch: WorkbenchDispatch): WebUiDisposer { + const root = createRoot(container); + root.render(); + return () => root.unmount(); + }, formatters: { score: (value: unknown) => _score(value), lag: (value: unknown) => _lag(value), "mono-time": (value: unknown) => _shortTs(value), }, -}); +} satisfies WorkbenchPanelEntry; + +export function activate(ctx: WebHostContextV1): WebUiDisposer { + dashboardRequest = ctx.http.request; + const release = ctx.ui.inject("workbench.panels.v2", (mount) => mount.register(panel)); + return () => { + release(); + dashboardRequest = null; + }; +} diff --git a/plugin.py b/plugin.py index d27808f..5db52ca 100644 --- a/plugin.py +++ b/plugin.py @@ -57,7 +57,7 @@ api_version = 3 name = "proactive_feedback" -version = "3.0.0" +version = "3.0.1" desc = "记录主动消息被继续的反馈,并提供桌面与移动只读投影。" author = "Akashic" inject = (SESSION_READ, UI_SLOTS, EMBEDDINGS) @@ -65,6 +65,12 @@ drift_skill_roots: tuple[str, ...] = () workspace_roots: tuple[str, ...] = () dashboard_module = "dashboard.py" +web_module = "web_module.js" +web_requires = ("workbench.panels.v2",) +web_provides = () +web_contract_digests = { + "workbench.panels.v2": "fb6417c9bf532c1fdb344767d06065d5d3293da85deb64eff1e8088889a33bcb", +} async def apply(ctx: Context, config: object) -> None: diff --git a/tests/test_plugin.py b/tests/test_plugin.py index e1046aa..eb4022b 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1122,8 +1122,10 @@ async def test_plugin_installs_and_loads_from_ordinary_cache(tmp_path: Path) -> core_plugins = Path(inspect.getfile(PluginManager)).parents[2] / "plugins" manager = PluginManager( plugin_dirs=[ + core_plugins / "shell_ui", core_plugins / "models", core_plugins / "openai_compatible", + core_plugins / "workbench_ui", ], event_bus=EventBus(), tool_registry=None, @@ -1162,6 +1164,8 @@ async def test_manager_stable_candidate_ui_dashboard_and_cleanup(tmp_path: Path) "scorer.py", "mobile_panel.js", "mobile_panel.css", + "web_module.js", + "web_module.css", "akashic.plugin.toml", ): shutil.copy2(Path(__file__).parents[1] / filename, plugin_dir / filename) @@ -1172,8 +1176,10 @@ async def test_manager_stable_candidate_ui_dashboard_and_cleanup(tmp_path: Path) manager = PluginManager( plugin_dirs=[ tmp_path / "plugins", + core_plugins / "shell_ui", core_plugins / "models", core_plugins / "openai_compatible", + core_plugins / "workbench_ui", ], event_bus=EventBus(), tool_registry=None, @@ -1231,9 +1237,12 @@ async def test_manager_stable_candidate_ui_dashboard_and_cleanup(tmp_path: Path) candidate_snapshot = candidate.runtime_snapshot assert candidate_snapshot.mobile_ui_registry is not None dashboard_host.prepare_snapshot(candidate_snapshot) - assert len(candidate_snapshot.dashboard_bindings) == 1 - binding = candidate_snapshot.dashboard_bindings[0] - assert isinstance(binding, DashboardBinding) + binding = next( + item + for item in candidate_snapshot.dashboard_bindings + if isinstance(item, DashboardBinding) + and item.plugin_id == "proactive_feedback" + ) assert binding.validation is True assert binding.runtime_data_root is not None assert binding.runtime_data_root != formal_data.resolve() diff --git a/web_module.css b/web_module.css new file mode 100644 index 0000000..6047616 --- /dev/null +++ b/web_module.css @@ -0,0 +1 @@ +.feedback-empty{padding:24px 16px}.feedback-empty__title{color:var(--ak-color-text-primary);font-size:20px;font-weight:600;letter-spacing:-.025em;line-height:1.25}.feedback-empty__text{margin-block-start:8px;color:var(--ak-color-text-secondary);font-size:13px;line-height:1.625}.feedback-detail{display:grid;gap:20px;padding:24px;color:var(--ak-color-text-primary)}.feedback-detail__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.feedback-detail__header p{margin:0 0 4px;color:var(--ak-color-action-primary);font-size:11px;font-weight:700;letter-spacing:.08em}.feedback-detail__header h2{margin:0;font-size:20px;letter-spacing:-.02em}.feedback-detail__header span{display:block;margin-block-start:5px;color:var(--ak-color-text-muted);font-size:12px}.feedback-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-block:1px solid var(--ak-color-border-default)}.feedback-summary>div{display:grid;gap:5px;padding:14px}.feedback-summary>div+div{border-inline-start:1px solid var(--ak-color-border-default)}.feedback-summary span{color:var(--ak-color-text-muted);font-size:11px}.feedback-summary strong{font:650 18px var(--mono)}.feedback-timeline{display:grid;gap:0}.feedback-timeline>h3{margin:0 0 10px;font-size:15px}.feedback-step{position:relative;display:grid;grid-template-columns:30px minmax(0,1fr);gap:12px;padding-block:10px}.feedback-step:not(:last-child):after{position:absolute;top:40px;bottom:-10px;left:14px;width:1px;content:"";background:var(--ak-color-border-strong)}.feedback-step__index{display:grid;width:30px;height:30px;place-items:center;border:1px solid var(--ak-color-border-strong);border-radius:50%;color:var(--ak-color-text-muted);background:var(--ak-color-bg-canvas);font:650 11px var(--mono)}.feedback-step.is-emphasis .feedback-step__index{border-color:var(--ak-color-action-primary);color:var(--ak-color-on-action-primary);background:var(--ak-color-action-soft)}.feedback-step h4{margin:3px 0 6px;font-size:13px}.feedback-step p{margin:0;color:var(--ak-color-text-secondary);font-size:13px;line-height:1.65;white-space:pre-wrap}.feedback-step.is-emphasis p{padding:12px;border-inline-start:3px solid var(--ak-color-action-primary);background:var(--ak-color-action-soft)}.feedback-technical{border-block-start:1px solid var(--ak-color-border-default)}.feedback-technical summary{padding:14px 2px;font-size:13px;font-weight:650;cursor:pointer}.feedback-technical summary:focus-visible{outline:2px solid var(--ak-color-action-primary);outline-offset:2px}.feedback-technical dl{display:grid;gap:8px;margin:0 0 14px}.feedback-technical dl>div{display:grid;grid-template-columns:110px minmax(0,1fr);gap:12px}.feedback-technical dt{color:var(--ak-color-text-muted);font-size:12px}.feedback-technical dd{overflow-wrap:anywhere;margin:0;font:12px var(--mono)}@media(max-width:720px){.feedback-detail{padding:18px}.feedback-detail__header{flex-direction:column}.feedback-summary{grid-template-columns:1fr}.feedback-summary>div+div{border-block-start:1px solid var(--ak-color-border-default);border-inline-start:0}.feedback-technical dl>div{grid-template-columns:1fr;gap:3px}} diff --git a/web_module.js b/web_module.js new file mode 100644 index 0000000..bc6e6a3 --- /dev/null +++ b/web_module.js @@ -0,0 +1 @@ +import{createRoot as k}from"react-dom/client";import{jsx as n,jsxs as a}from"react/jsx-runtime";var o=null;async function c(e,t){if(!o)throw new Error("主动反馈工作台面板未激活");let r=await o(e,t),i=await r.json();if(!r.ok)throw new Error(String(i.detail??i.message??`HTTP ${r.status}`));return i}function m(e){return typeof e=="number"?e.toFixed(3):"-"}function x(e){let t=String(e||"");if(!t)return"-";let r=new Date(t);return Number.isNaN(r.getTime())?t:`${r.getMonth()+1}-${String(r.getDate()).padStart(2,"0")} ${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}`}function f(e){return typeof e!="number"?"-":e<60?`${e}s`:e<3600?`${Math.round(e/60)}m`:`${(e/3600).toFixed(1)}h`}function h(e){return e==="explicit_quote"?"accent":e==="topic_follow"?"success":e==="unscored"?"warning":"muted"}function g(e){let t=String(e||"");return t==="explicit_quote"?"明确引用":t==="topic_follow"?"话题延续":t==="no_topic_follow"?"未延续话题":t==="unscored"?"待评分":t||"-"}function b(e){let t=String(e||"");return t==="gold"?"金标":t==="high"?"高":t==="medium"?"中":t==="low"?"低":t||"-"}function d(e){return String(e??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function u(e){let t=String(e||"").trim();return d(t||"-")}function v(e){let t=String(e||"");return`${d(g(t))}`}function w(e){let t=String(e||"");return t==="gold"||t==="high"?"success":t==="medium"?"warning":"muted"}function y(e){let t=String(e||"-");return`${d(b(t))}`}function _(e){let t=e.item,r=e.ui.Chip;if(!t)return a("div",{className:"feedback-empty",children:[n("div",{className:"feedback-empty__title",children:"反馈详情"}),n("div",{className:"feedback-empty__text",children:"点开一条记录后,这里会显示用户回复、命中的 proactive 和助手后续回复。"})]});let i=String(t.feedback_type||"");return a("main",{className:"feedback-detail","aria-labelledby":"feedback-detail-title",children:[a("header",{className:"feedback-detail__header",children:[a("div",{children:[n("p",{children:"主动消息效果"}),n("h2",{id:"feedback-detail-title",children:"用户如何回应这次主动触达"}),n("span",{children:String(t.session_key||"未关联会话")})]}),n(r,{tone:h(i),children:g(i)})]}),a("section",{className:"feedback-summary","aria-label":"反馈判断摘要",children:[n(l,{label:"置信度",value:b(t.confidence)}),n(l,{label:"响应延迟",value:f(t.lag_seconds)}),n(l,{label:"PUA 分数",value:m(t.pua_score)})]}),a("section",{className:"feedback-timeline","aria-labelledby":"feedback-timeline-title",children:[n("h3",{id:"feedback-timeline-title",children:"对话链路"}),n(s,{index:"1",title:"主动消息",text:String(t.proactive_preview||t.quoted_preview||"")}),n(s,{index:"2",title:"用户反馈",text:String(t.user_reply_preview||t.user_preview||""),emphasis:!0}),n(s,{index:"3",title:"助手后续",text:String(t.assistant_preview||"")})]}),a("details",{className:"feedback-technical",children:[n("summary",{children:"查看匹配依据与消息标识"}),a("dl",{children:[a("div",{children:[n("dt",{children:"匹配方式"}),n("dd",{children:String(t.matched_by||"-")})]}),a("div",{children:[n("dt",{children:"用户消息 ID"}),n("dd",{children:String(t.user_message_id||"-")})]}),a("div",{children:[n("dt",{children:"主动消息 ID"}),n("dd",{children:String(t.proactive_message_id||"-")})]})]})]})]})}function l(e){return a("div",{children:[n("span",{children:e.label}),n("strong",{children:e.value})]})}function s(e){return a("div",{className:`feedback-step${e.emphasis?" is-emphasis":""}`,children:[n("span",{className:"feedback-step__index",children:e.index}),a("div",{children:[n("h4",{children:e.title}),n("p",{children:e.text||"没有可展示的内容"})]})]})}var S={id:"proactive-feedback",label:"主动反馈",viewLabel:"主动反馈",order:70,pageSize:50,rowKey:"id",countTitle(e){return`共 ${e} 条反馈`},columns:[{key:"created_at",label:"时间",width:96,fmt:"mono-time",cellClass:"mono cell-time",rawTitle:!0},{key:"feedback_type",label:"类型",width:126,renderCell:v},{key:"confidence",label:"置信度",width:84,renderCell:y},{key:"lag_seconds",label:"延迟",width:68,fmt:"lag",cellClass:"mono cell-metric",align:"right"},{key:"user_reply_preview",label:"用户回复",flex:!0,renderCell:u,cellClass:"content-preview",rawTitle:!0},{key:"proactive_preview",label:"命中内容",flex:!0,renderCell:u,cellClass:"content-preview",rawTitle:!0}],async getCount({signal:e}){try{return(await c("/api/dashboard/proactive-feedback/overview",{signal:e})).total||0}catch(t){if(e.aborted)throw t;return null}},async fetchPage({page:e,pageSize:t,signal:r}){let i=new URLSearchParams;i.set("page",String(e)),i.set("page_size",String(t));let p=await c(`/api/dashboard/proactive-feedback/events?${i.toString()}`,{signal:r});return{items:p.items||[],total:p.total||0}},async fetchDetail(e,{signal:t}){return c(`/api/dashboard/proactive-feedback/events/${e.id}`,{signal:t})},renderDetail(e,t,r){let i=k(t);return i.render(n(_,{item:e,ui:r.ui})),()=>i.unmount()},formatters:{score:e=>m(e),lag:e=>f(e),"mono-time":e=>x(e)}};function N(e){o=e.http.request;let t=e.ui.inject("workbench.panels.v2",r=>r.register(S));return()=>{t(),o=null}}export{N as activate};