feat: open source multi-user AI notebook with observability

This commit is contained in:
wuyang
2026-07-28 14:28:00 +08:00
commit 935c411847
30 changed files with 6522 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
export type Fragment = {
id: string;
content: string;
created_at: string;
};
export type User = {
id: string;
label: string;
role: "admin" | "member";
debug_sharing: boolean;
};
export type Snapshot = {
maturity_ai: number;
motion: string;
position: string;
tension: string;
trajectory: string;
possible_moves: string[];
created_at: string;
};
export type Idea = {
id: string;
title: string;
summary: string;
maturity_ai: number;
maturity_override: number | null;
maturity: number;
is_overridden: boolean;
confidence: number;
motion: string;
position: string;
tension: string;
trajectory: string;
possible_moves: string[];
fragment_count: number;
latest_fragment_at: string | null;
updated_at: string;
fragments?: Fragment[];
snapshots?: Snapshot[];
};
export type AdminOverview = {
totals: {
users: number;
fragments: number;
ideas: number;
pending: number;
audit_events: number;
};
last_24h: {
runs: number;
successes: number;
errors: number;
success_rate: number;
avg_duration_ms: number;
input_tokens: number;
output_tokens: number;
reasoning_tokens: number;
cached_tokens: number;
model_rounds: number;
tool_calls: number;
};
runtime: {
uptime_seconds: number;
curator_configured: boolean;
queue_depth: number;
model: string;
prompt_version: string;
};
daily: Array<{
day: string;
runs: number;
successes: number;
errors: number;
reasoning_tokens: number;
}>;
recent_errors: Array<{
id: string;
started_at: string;
error_type: string;
error_message: string;
user_label: string;
}>;
audit_24h: {
events: number;
failed_logins: number;
};
narrative: string;
};
export type AgentRun = {
id: string;
user_id: string;
fragment_id: string;
status: "running" | "success" | "error";
prompt_version: string;
model: string;
thinking_effort: string;
started_at: string;
finished_at: string | null;
duration_ms: number | null;
attempt_count: number;
model_rounds: number;
tool_calls: number;
search_calls: number;
inspection_calls: number;
input_tokens: number;
output_tokens: number;
reasoning_tokens: number;
cached_tokens: number;
error_type: string | null;
error_message: string | null;
user_label: string;
user_role: string;
debug_sharing: boolean;
content_length: number;
};
export type AgentEvent = {
id: string;
event_type: string;
event_at: string;
duration_ms: number | null;
payload: Record<string, unknown>;
};
export type AgentRunDetail = {
run: AgentRun & { content_sha256: string };
fragment:
| { content_visible: true; content: string }
| { content_visible: false; content_length: number; content_sha256: string };
events: AgentEvent[];
privacy: {
content_visible: boolean;
reasoning_content_stored: boolean;
reason: string;
};
};
export type AdminUser = User & {
created_at: string;
fragment_count: number;
idea_count: number;
};
export type AuditEvent = {
id: string;
event_type: string;
created_at: string;
user_label: string | null;
payload: Record<string, unknown>;
};
type ApiOptions = RequestInit & { body?: string };
export class ApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
async function request<T>(path: string, options: ApiOptions = {}): Promise<T> {
const response = await fetch(path, {
...options,
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-Note-Client": "xuxiang-web",
...(options.headers ?? {}),
},
});
if (!response.ok) {
let message = "请求没有完成";
try {
const body = await response.json();
if (typeof body.detail === "string") message = body.detail;
} catch {
// Keep the calm fallback message.
}
throw new ApiError(response.status, message);
}
return response.json() as Promise<T>;
}
export const api = {
session: () =>
request<{ authenticated: boolean; user: User }>("/api/session"),
login: (accessKey: string) =>
request<{ authenticated: boolean; user: User }>("/api/login", {
method: "POST",
body: JSON.stringify({ access_key: accessKey }),
}),
logout: () =>
request<{ authenticated: boolean }>("/api/logout", { method: "POST" }),
fragments: () => request<{ items: Fragment[] }>("/api/fragments"),
createFragment: (content: string) =>
request<Fragment>("/api/fragments", {
method: "POST",
body: JSON.stringify({ content }),
}),
ideas: () => request<{ items: Idea[] }>("/api/ideas"),
idea: (id: string) => request<Idea>(`/api/ideas/${id}`),
updateMaturity: (id: string, maturity: number | null) =>
request<Idea>(`/api/ideas/${id}/position`, {
method: "PATCH",
body: JSON.stringify({ maturity }),
}),
status: () =>
request<{ pending: number; curator_configured: boolean }>(
"/api/system/status",
),
updateDebugSharing: (enabled: boolean) =>
request<User>("/api/account/debug-sharing", {
method: "PATCH",
body: JSON.stringify({ enabled }),
}),
adminOverview: () => request<AdminOverview>("/api/admin/overview"),
adminUsers: () => request<{ items: AdminUser[] }>("/api/admin/users"),
adminRuns: (limit = 80) =>
request<{ items: AgentRun[] }>(`/api/admin/runs?limit=${limit}`),
adminRun: (id: string) =>
request<AgentRunDetail>(`/api/admin/runs/${id}`),
adminAudit: (limit = 80) =>
request<{ items: AuditEvent[] }>(`/api/admin/audit?limit=${limit}`),
};