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: 9da3a988a2bf62b0f550bd4f6bb98c4eeb1f56f5
ref: d5e5177092d74de03588d8675f3504db36b7bacb
path: .akashic-core
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion akashic.plugin.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
schema_version = 1
name = "emotion"
version = "3.0.2"
version = "3.0.4"
api_version = 3
entrypoint = "plugin.py"
19 changes: 19 additions & 0 deletions dashboard_panel.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
.emotion-empty {
padding: 24px 16px;
}

.emotion-empty__title {
color: var(--ak-color-text-primary);
font-size: 20px;
font-weight: 600;
letter-spacing: -0.025em;
line-height: 1.25;
}

.emotion-empty__text {
margin-block-start: 8px;
color: var(--ak-color-text-secondary);
font-size: 13px;
line-height: 1.625;
}

.emotion-detail {
display: grid;
gap: 18px;
Expand Down
147 changes: 0 additions & 147 deletions dashboard_panel.js

This file was deleted.

58 changes: 43 additions & 15 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("Emotion 工作台面板未激活");
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 {
state: Record<string, unknown> | null;
Expand Down Expand Up @@ -48,13 +60,14 @@ function _effectLabel(value: unknown): string {
function _toneCell(value: unknown): string {
const text = String(value || "-");
const tone = text === "raise_send_bar" ? "warning" : text === "lower_send_bar" ? "success" : "muted";
return `<span class="${window.AkashicDashboard.ui.cx.badge(tone)}">${_escape(_effectLabel(text))}</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(_effectLabel(text))}</span>`;
}

function EmotionDetail(props: { item: Record<string, unknown> | null }): ReactElement {
function EmotionDetail(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">选择一条记录,查看这次主动任务的情绪影响。</div></div>;
return <div className="emotion-empty"><div className="emotion-empty__title">情绪影响详情</div><div className="emotion-empty__text">选择一条记录,查看这次主动任务的情绪影响。</div></div>;
}
const delta = typeof item.threshold_delta === "number" ? item.threshold_delta : null;
return (
Expand Down Expand Up @@ -114,10 +127,11 @@ function TextDisclosure(props: { title: string; text: string }): ReactElement {
);
}

window.AkashicDashboard.registerPlugin({
const panel = {
id: "emotion",
label: "情绪决策",
viewLabel: "情绪决策",
order: 30,
pageSize: 50,
rowKey: "id",

Expand All @@ -135,32 +149,46 @@ window.AkashicDashboard.registerPlugin({
{ key: "tick_id", label: "任务", flex: true, cellClass: "mono 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/emotion/overview");
const overview = await api<Overview>("/api/dashboard/emotion/overview", { signal });
return overview.effect_count || 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/emotion/effects?${params.toString()}`);
const data = await api<FetchPage>(`/api/dashboard/emotion/effects?${params.toString()}`, { signal });
return { items: data.items || [], total: data.total || 0 };
},

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

Detail: EmotionDetail,
renderDetail(item: Record<string, unknown> | null, container: HTMLElement, dispatch: WorkbenchDispatch): WebUiDisposer {
const root = createRoot(container);
root.render(<EmotionDetail item={item} ui={dispatch.ui} />);
return () => root.unmount();
},

formatters: {
score: (value: unknown) => _score(value),
delta: (value: unknown) => _delta(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;
};
}
4 changes: 3 additions & 1 deletion db.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ def prepare_drift_proposal(
# 1. Replay a locally prepared/submitted proposal before admitting new evidence.
existing = conn.execute(
"""
SELECT proposal_id, revision, payload_json
SELECT proposal_id, revision, payload_json, created_at
FROM emotion_drift_runs
WHERE status IN ('prepared', 'submitted')
ORDER BY created_at, proposal_id, revision LIMIT 1
Expand Down Expand Up @@ -546,6 +546,7 @@ def prepare_drift_proposal(
"proposal_id": proposal_id,
"revision": revision,
"payload": payload,
"due_at": _aware_datetime(now),
}


Expand Down Expand Up @@ -722,6 +723,7 @@ def _drift_proposal_from_row(row: sqlite3.Row) -> dict[str, object]:
"proposal_id": str(row["proposal_id"]),
"revision": str(row["revision"]),
"payload": payload,
"due_at": _aware_datetime(datetime.fromisoformat(str(row["created_at"]))),
}


Expand Down
8 changes: 7 additions & 1 deletion plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@

api_version = 3
name = "emotion"
version = "3.0.2"
version = "3.0.4"
desc = "Timer-refreshed Emotion context and ordinary Drift preference projection."
DRIFT_PROPOSALS = ServiceKey[DriftProposalServices]("drift.proposals.v1")
DRIFT_WAKE = ServiceKey[DriftWakeServices]("drift.wake.v1")
inject = (TIMERS, TOOL_CATALOG, UI_SLOTS, DRIFT_PROPOSALS, DRIFT_WAKE)
workspace_roots = ("emotion",)
drift_skill_roots = ("drift/skills",)
dashboard_module = "dashboard.py"
web_module = "web_module.js"
web_requires = ("workbench.panels.v2",)
web_provides = ()
web_contract_digests = {
"workbench.panels.v2": "fb6417c9bf532c1fdb344767d06065d5d3293da85deb64eff1e8088889a33bcb",
}
_v3_emotion_root: Path | None = None
_v3_emotion_runtime: EmotionRuntime | None = None

Expand Down
5 changes: 3 additions & 2 deletions runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ async def tick_once(self) -> None:
proposal_id = cast(str, proposal["proposal_id"])
revision = cast(str, proposal["revision"])
payload = cast(Mapping[str, object], proposal["payload"])
due_at = cast(datetime, proposal["due_at"])
_ = self._proposals.propose(
proposal_id,
revision,
payload,
now,
next_due=now + _REFRESH_INTERVAL,
due_at,
next_due=due_at + _REFRESH_INTERVAL,
)
db = open_db(self._db_path)
try:
Expand Down
Loading
Loading