Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/plugin-api-v3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion akashic.plugin.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema_version = 1
name = "proactive_feedback"
version = "3.0.0"
version = "3.0.1"
api_version = 3
entrypoint = "plugin.py"

Expand Down
19 changes: 19 additions & 0 deletions dashboard_panel.css
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
169 changes: 0 additions & 169 deletions dashboard_panel.js

This file was deleted.

63 changes: 46 additions & 17 deletions dashboard_panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
/// <reference path="../../types/akashic-dashboard.d.ts" />
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<T = unknown>(path: string, init?: RequestInit): Promise<T> {
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;
Expand Down Expand Up @@ -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 `<span class="${window.AkashicDashboard.ui.cx.badge(tone)}">${_escape(_feedbackTypeLabel(type))}</span>`;
return `<span class="ak-chip ak-chip--${tone} inline-flex items-center gap-1.5 px-2.5 py-1 font-sans text-[11px] tabular-nums">${_escape(_feedbackTypeLabel(type))}</span>`;
}

function _confidenceTone(value: unknown): "success" | "warning" | "muted" {
Expand All @@ -85,13 +97,15 @@ function _confidenceTone(value: unknown): "success" | "warning" | "muted" {

function _confidenceCell(value: unknown): string {
const confidence = String(value || "-");
return `<span class="${window.AkashicDashboard.ui.cx.badge(_confidenceTone(confidence))}">${_escape(_confidenceLabel(confidence))}</span>`;
const tone = _confidenceTone(confidence);
return `<span class="ak-chip ak-chip--${tone} inline-flex items-center gap-1.5 px-2.5 py-1 font-sans text-[11px] tabular-nums">${_escape(_confidenceLabel(confidence))}</span>`;
}

function FeedbackDetail(props: { item: Record<string, unknown> | null }): ReactElement {
function FeedbackDetail(props: { item: Record<string, unknown> | null; ui: WorkbenchUi }): ReactElement {
const item = props.item;
const Chip = props.ui.Chip;
if (!item) {
return <div className="detail-empty"><div className="detail-empty-title">反馈详情</div><div className="detail-empty-text">点开一条记录后,这里会显示用户回复、命中的 proactive 和助手后续回复。</div></div>;
return <div className="feedback-empty"><div className="feedback-empty__title">反馈详情</div><div className="feedback-empty__text">点开一条记录后,这里会显示用户回复、命中的 proactive 和助手后续回复。</div></div>;
}
const type = String(item.feedback_type || "");
return (
Expand Down Expand Up @@ -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",

Expand All @@ -166,32 +181,46 @@ window.AkashicDashboard.registerPlugin({
{ key: "proactive_preview", label: "命中内容", flex: true, renderCell: _cellText, cellClass: "content-preview", rawTitle: true },
],

async getCount(): Promise<number | null> {
async getCount({ signal }: { signal: AbortSignal }): Promise<number | null> {
try {
const overview = await api<Overview>("/api/dashboard/proactive-feedback/overview");
const overview = await api<Overview>("/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<FetchPage>(`/api/dashboard/proactive-feedback/events?${params.toString()}`);
const data = await api<FetchPage>(`/api/dashboard/proactive-feedback/events?${params.toString()}`, { signal });
return { items: data.items || [], total: data.total || 0 };
},

async fetchDetail(item: Record<string, unknown>) {
return api<Record<string, unknown>>(`/api/dashboard/proactive-feedback/events/${item.id}`);
async fetchDetail(item: Record<string, unknown>, { signal }: { signal: AbortSignal }) {
return api<Record<string, unknown>>(`/api/dashboard/proactive-feedback/events/${item.id}`, { signal });
},

Detail: FeedbackDetail,
renderDetail(item: Record<string, unknown> | null, container: HTMLElement, dispatch: WorkbenchDispatch): WebUiDisposer {
const root = createRoot(container);
root.render(<FeedbackDetail item={item} ui={dispatch.ui} />);
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;
};
}
8 changes: 7 additions & 1 deletion plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,20 @@

api_version = 3
name = "proactive_feedback"
version = "3.0.0"
version = "3.0.1"
desc = "记录主动消息被继续的反馈,并提供桌面与移动只读投影。"
author = "Akashic"
inject = (SESSION_READ, UI_SLOTS, EMBEDDINGS)
skill_roots: tuple[str, ...] = ()
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:
Expand Down
15 changes: 12 additions & 3 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
Loading
Loading