Users, file manager, image generation, budgets and agent workflow
- User management: local accounts (Argon2) and OpenID Connect, admin/user roles, per-user folders plus admin-shared folders, read-only session sharing, event filtering per user - File manager with CodeMirror editor and git client (status, diff, stage, commit, branches, fetch/pull/push, log, clone/init) - Image generation via fal.ai: generate_image tool and image page - Budgets: monthly limits per provider, provider balances/costs (OpenRouter, Anthropic admin cost report, fal.ai), price discovery - Subagents on other models, background tasks, concurrency limits per host/model/subagent - MCP tools loaded on demand (mcp_search/mcp_call), read-only hints - Permission mode "auto", safe read-only commands always allowed - Persistent message queue with attachments, deletable - Live reasoning incl. <think> tags, activity animations, CSS themes, mobile layout, statistics per user, upload size handling - Docker: named volume for state, localhost-bound port Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f3a2fe3816
commit
49262c612c
70 files changed
+8495
-714
No files matched your search
@@ -2,6 +2,7 @@ import { useT } from "../i18n";
|
||||
import { navigate } from "../router";
|
||||
import { useStore } from "../store";
|
||||
import type { Part, Todo } from "../types";
|
||||
import { Spinner } from "./Activity";
|
||||
import { Diff } from "./Diff";
|
||||
|
||||
type ToolCall = Extract<Part, { type: "tool_call" }>;
|
||||
@@ -63,23 +64,34 @@ export function ToolCard({ call, result, running }: { call: ToolCall; result?: T
|
||||
const statusText = result ? (result.is_error ? "✗" : "✓") : running ? "…" : "–";
|
||||
const meta = result?.meta;
|
||||
|
||||
// The todo list is shown in its own panel; keep the card compact.
|
||||
// The live task list has its own panel; updates are a single line here.
|
||||
if (call.name === "todo_write" && !result?.is_error) {
|
||||
const todos: Todo[] = call.input?.todos ?? [];
|
||||
const done = todos.filter((x) => x.status === "completed").length;
|
||||
const current = todos.find((x) => x.status === "in_progress");
|
||||
return (
|
||||
<div className="todos">
|
||||
<div style={{ color: "var(--muted)", fontSize: 12 }}>{t("tool.todos")}</div>
|
||||
<TodoList todos={call.input?.todos ?? []} />
|
||||
<div className="todo-line">
|
||||
☑ {t("tool.todosUpdated", { done, total: todos.length })}
|
||||
{current && <span> · ▶ {current.content}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Short diffs are shown right away, like in a terminal agent.
|
||||
const diffLines = typeof meta?.diff === "string" ? meta.diff.split("\n").length : 0;
|
||||
const openByDefault =
|
||||
(status === "err" && !!result) ||
|
||||
(!!result && !result.is_error && ((diffLines > 0 && diffLines <= 40) || call.name === "generate_image"));
|
||||
|
||||
return (
|
||||
<details className="tool" open={status === "err" && !!result}>
|
||||
<details className={`tool ${status === "run" ? "running" : ""}`} open={openByDefault}>
|
||||
<summary>
|
||||
<span className="tool-name">{call.name}</span>
|
||||
<span className="tool-title">{toolTitle(call.name, call.input)}</span>
|
||||
{meta?.similarity !== undefined && <span className="chip">{meta.similarity} %</span>}
|
||||
<span className={`status ${status}`}>{statusText}</span>
|
||||
{call.name === "task" && meta?.model && <span className="chip">{meta.model}</span>}
|
||||
{call.name === "task" && call.input?.background && <span className="chip">{t("tool.background")}</span>}
|
||||
{status === "run" ? <Spinner small /> : <span className={`status ${status}`}>{statusText}</span>}
|
||||
</summary>
|
||||
<div className="tool-body">
|
||||
{call.name === "bash" && <pre className="mono">$ {call.input?.command}</pre>}
|
||||
@@ -107,9 +119,17 @@ export function ToolCard({ call, result, running }: { call: ToolCall; result?: T
|
||||
<pre className="mono">{result.content}</pre>
|
||||
</>
|
||||
)}
|
||||
{result?.images?.map((img, i) => (
|
||||
<img key={i} src={`data:${img.media_type};base64,${img.data}`} alt="" />
|
||||
))}
|
||||
{Array.isArray(meta?.image_ids) ? (
|
||||
<div className="gallery small">
|
||||
{meta.image_ids.map((id: string) => (
|
||||
<a key={id} href={`/api/images/${id}/file`} target="_blank" rel="noreferrer" className="gallery-item">
|
||||
<img src={`/api/images/${id}/file`} alt="" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
result?.images?.map((img, i) => <img key={i} src={`data:${img.media_type};base64,${img.data}`} alt="" />)
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user