feat(web): D 消息编辑/删除前端入口 + 日志聚合前端(Observability后端tail) + 修api-client契约
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- D前端: lib/api/messages.ts(patchMessage/deleteMessage,兼容裸DTO与{message}) + MessageActions.tsx(仅user消息; 单击/右键/450ms长按开Miuix玻璃菜单; 编辑弹窗预填+字数→乐观updateMessageById→patchMessage→失败回滚; 删除确认→deleteMessage→removeMessageById; 乐观气泡禁用) + chat.tsx挂载 + Icon加edit/trash/more
- 日志前端: types(AdminLogLine time:string|number) + adminSystem.adminObservabilityLogs + Observability.tsx 后端分级tail(level筛选chip注明'该级及以上'+等宽行HH:mm:ss.SSS·level着色·source·msg+30s刷新) 下半保留WS回显
- Docs/api-client §6a/§4.2 修正: PATCH /messages/{id} 实际返回 {data:<DTO>} 非 {data:{message}}(前端已双向兼容,避免误导Android)
- 前端 build 绿(167 modules); SSR冒烟11 checks
- 遗留: 未实机登录; 菜单absolute理论上边缘可裁切(可改portal)
This commit is contained in:
AskaEth committed 2026-10-03 15:23:27 +08:00
1 parent 025dd1796b
commit 1ac126fc9e
9 files changed
+611 -12

No files matched your search

+4
View File
@@ -7,6 +7,7 @@
* auth.ts /auth/login, /health
* chat.ts /chat (sync + streaming)
* sessions.ts /sessions (list/create/delete, message history)
* messages.ts /messages/{id} (PATCH edit / DELETE)
* personas.ts /personas
* admin.ts /admin/* CRUD (personas, users, llm, rag, iot, automations…)
* adminChannels.ts /admin/channels/* (adapters, config, platform views, login)
@@ -41,6 +42,8 @@ export {
} from "./api/sessions";
export type { MessageDto } from "./api/sessions";
export { deleteMessage, patchMessage } from "./api/messages";
export { fetchPersonas } from "./api/personas";
export {
@@ -100,6 +103,7 @@ export {
adminBackthink,
adminListNotifications,
adminObservabilityAudit,
adminObservabilityLogs,
adminObservabilityMetrics,
adminObservabilityTrace,
adminSystemBackup,
+14
View File
@@ -1,4 +1,6 @@
import type {
AdminLogLine,
AdminLogQuery,
AdminNotification,
AdminNotificationQuery,
AdminObservabilityMetrics,
@@ -24,6 +26,18 @@ export function adminObservabilityTrace(limit?: number): Promise<AdminTraceSpan[
export function adminObservabilityAudit(limit?: number): Promise<AuditRecord[]> {
return request<AuditRecord[]>(`/admin/observability/audit${limitQuery(limit)}`);
}
/** GET /api/v1/admin/observability/logs?level=&limit= — the gateway's structured
* log tail, newest first (owner/admin only). An omitted `level` means “all”; the
* backend defaults `limit` to 200 and caps it at 1000. */
export function adminObservabilityLogs(
params: AdminLogQuery = {}
): Promise<AdminLogLine[]> {
const qs = new URLSearchParams();
if (params.level) qs.set("level", params.level);
if (params.limit !== undefined) qs.set("limit", String(params.limit));
const q = qs.toString();
return request<AdminLogLine[]>(`/admin/observability/logs${q ? `?${q}` : ""}`);
}
/** GET /api/v1/admin/backthink — persisted background-think records (newest first). */
export function adminBackthink(limit?: number): Promise<BackthinkItem[]> {
+36
View File
@@ -0,0 +1,36 @@
import type { MessageDto } from "./sessions";
import { request } from "./core";
/**
* Single-message mutations (design/16 §4.2 D3, Docs/api-client.md §6a/6b).
* Both succeed against the persisted row and broadcast the change over the WS
* hub (`message_edited` / `message_deleted`), so other clients converge without
* a re-fetch; the caller's REST response is only the acknowledgement.
*/
/** The gateway answers PATCH with `{data: <updated message DTO>}` (see
* gateway/history_write.go `writeJSON(w, 200, toHistoryMessage(msg))` and
* history_write_broadcast_test.go, which decodes `data` straight into the DTO).
* Docs/api-client.md §6a writes that envelope as `{data:{message}}`; the optional
* `message` field keeps this call working either way, so a plain DTO is never
* mistaken for an empty result (which would blank the bubble). */
type PatchMessageEnvelope = MessageDto & { message?: MessageDto };
/** PATCH /api/v1/messages/{id} — replace one message's `content`. The backend
* validates non-empty `≤4000` runes (400) and existence (404), and only ever
* changes `content`; role / timestamps / platform are preserved. */
export async function patchMessage(id: string, content: string): Promise<MessageDto> {
const res = await request<PatchMessageEnvelope>(
`/messages/${encodeURIComponent(id)}`,
{ method: "PATCH", body: JSON.stringify({ content }) }
);
return res.message ?? res;
}
/** DELETE /api/v1/messages/{id} — remove one message (404 when already gone).
* The response echoes the deleted id, so a caller can assert what it removed. */
export function deleteMessage(id: string): Promise<{ deleted: string }> {
return request<{ deleted: string }>(`/messages/${encodeURIComponent(id)}`, {
method: "DELETE",
});
}
+22
View File
@@ -401,6 +401,28 @@ export interface AdminLogRecord {
msg: string;
}
/** Severity filter of the live log tail (GET /api/v1/admin/observability/logs).
* An absent `level` means “all”. */
export type AdminLogLevel = "debug" | "info" | "warn" | "error";
/** One line of the gateway's structured log ring buffer (newest first).
* `time` is RFC3339 on current builds but the field is typed as either that or
* epoch milliseconds so the renderer stays compatible with both; `source` is
* the `module/subfeature` tag every log line carries (never blank). */
export interface AdminLogLine {
time: string | number;
level: string;
source: string;
msg: string;
}
/** Query for GET /api/v1/admin/observability/logs. `limit` defaults to 200 and
* is clamped to 1000 by the backend. */
export interface AdminLogQuery {
level?: AdminLogLevel;
limit?: number;
}
/** A knowledge-base document (GET /api/v1/admin/rag). */
export interface AdminRagDoc {
id: string;