large update

This commit is contained in:
ZacharyZhang-NY committed 2026-03-29 14:08:15 -04:00
1 parent 2ed952f19c
commit 65e85135c1
111 files changed
+3936 -2933

No files matched your search

@@ -0,0 +1,204 @@
import { useCallback, useEffect, useState } from "react";
import { FloppyDisk, Plus, X as XIcon } from "@phosphor-icons/react";
import type { ConnectorRecord, ProfileSnapshot } from "@shared/types";
import type { AppLocale } from "@shared/locale";
import { unwrap } from "../../lib/desktop";
import { ActionButton } from "../ui";
interface ModelManagementProps {
connectors: ProfileSnapshot["connectors"];
locale: AppLocale;
onSaved: () => Promise<void>;
}
/** Check if a claude_compat connector has been configured (has env bindings). */
function isConfigured(c: ConnectorRecord): boolean {
return c.envBindingText.includes("ANTHROPIC_BASE_URL") || c.envBindingText.includes("ANTHROPIC_API_KEY");
}
function getDisplayName(connector: ConnectorRecord): string {
if (connector.id.startsWith("claude_compat") && connector.notes) return connector.notes;
return connector.label;
}
function CompatModelEditor({
connector,
locale,
onSaved,
}: {
connector: ConnectorRecord;
locale: AppLocale;
onSaved: () => Promise<void>;
}) {
const [models, setModels] = useState<string[]>(() => {
const raw = connector.model ?? "";
return raw.split(",").map((m) => m.trim()).filter(Boolean);
});
const [newModel, setNewModel] = useState("");
const [saving, setSaving] = useState(false);
const [dirty, setDirty] = useState(false);
useEffect(() => {
const raw = connector.model ?? "";
setModels(raw.split(",").map((m) => m.trim()).filter(Boolean));
setDirty(false);
}, [connector.model]);
function addModel() {
const trimmed = newModel.trim();
if (!trimmed || models.includes(trimmed)) return;
setModels((prev) => [...prev, trimmed]);
setNewModel("");
setDirty(true);
}
function removeModel(m: string) {
setModels((prev) => prev.filter((x) => x !== m));
setDirty(true);
}
async function handleSave() {
setSaving(true);
try {
unwrap(await window.agentCompany.saveConnector({
id: connector.id,
command: connector.command,
model: models.join(",") || null,
envBindingText: connector.envBindingText,
notes: connector.notes,
}));
setDirty(false);
await onSaved();
} finally {
setSaving(false);
}
}
return (
<div>
<div className="mb-2 text-[13px] font-semibold text-foreground">{getDisplayName(connector)}</div>
{models.length > 0 ? (
<div className="mb-2 flex flex-wrap gap-1.5">
{models.map((m) => (
<span
key={m}
className="inline-flex items-center gap-1 rounded-md border border-border bg-muted px-2.5 py-1 text-[12px] text-foreground"
>
{m}
<button
type="button"
onClick={() => removeModel(m)}
className="ml-0.5 text-muted-foreground transition hover:text-destructive"
aria-label={`Remove ${m}`}
>
<XIcon size={10} />
</button>
</span>
))}
</div>
) : (
<div className="mb-2 text-[12px] text-muted-foreground">
{locale === "zh" ? "未配置模型" : "No models configured"}
</div>
)}
<div className="flex items-center gap-2">
<input
className="flex-1 rounded-lg border border-border bg-card px-3 py-2 text-[13px] text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
value={newModel}
onChange={(e) => setNewModel(e.target.value)}
placeholder={locale === "zh" ? "输入模型名称" : "model-name"}
onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addModel(); } }}
/>
<button
type="button"
onClick={addModel}
disabled={!newModel.trim()}
className="shrink-0 rounded-lg border border-border bg-card p-2 text-foreground transition hover:bg-muted disabled:opacity-40"
aria-label="Add model"
>
<Plus size={14} />
</button>
{dirty ? (
<ActionButton
label={locale === "zh" ? "保存" : "Save"}
tone="accent"
icon={FloppyDisk}
onClick={() => void handleSave()}
loading={saving}
/>
) : null}
</div>
</div>
);
}
function NativeModelList({
connector,
locale,
}: {
connector: ConnectorRecord;
locale: AppLocale;
}) {
const [models, setModels] = useState<Array<{ id: string; label: string }>>([]);
const loadModels = useCallback(async () => {
try {
const result = await window.agentCompany.listConnectorModels(connector.id);
if (result.ok && result.data.length > 0) {
setModels(result.data);
}
} catch { /* non-fatal */ }
}, [connector.id]);
useEffect(() => { void loadModels(); }, [loadModels]);
return (
<div>
<div className="mb-2 text-[13px] font-semibold text-foreground">{getDisplayName(connector)}</div>
{models.length > 0 ? (
<div className="flex flex-wrap gap-1.5">
{models.map((m) => (
<span
key={m.id}
className="inline-block rounded-md border border-border bg-muted px-2.5 py-1 text-[12px] text-foreground"
>
{m.label}
</span>
))}
</div>
) : (
<div className="text-[12px] text-muted-foreground">
{locale === "zh" ? "使用默认模型" : "Using default model"}
</div>
)}
</div>
);
}
export function ModelManagement({ connectors, locale, onSaved }: ModelManagementProps) {
// Show native connectors that are ready + claude_compat connectors that are configured
const visibleConnectors = connectors.filter((c) => {
if (c.id.startsWith("claude_compat")) return isConfigured(c);
return c.status === "ready";
});
if (visibleConnectors.length === 0) {
return (
<div className="py-6 text-center text-[13px] text-muted-foreground">
{locale === "zh" ? "暂无就绪的提供商" : "No ready providers"}
</div>
);
}
return (
<div className="space-y-5">
{visibleConnectors.map((connector) =>
connector.id.startsWith("claude_compat") ? (
<CompatModelEditor key={connector.id} connector={connector} locale={locale} onSaved={onSaved} />
) : (
<NativeModelList key={connector.id} connector={connector} locale={locale} />
),
)}
</div>
);
}
@@ -21,7 +21,7 @@ export function TaskCreator({ companyId, goals, projects, agents, workspaces, on
}
return (
<div className="grid gap-4 md:grid-cols-2">
<div className="grid min-w-0 gap-4 md:grid-cols-2">
<Input label={t("exec.title")} value={title} onChange={setTitle} placeholder={t("exec.taskTitle")} />
<Select label={t("exec.priority")} value={priority} onChange={setPriority} options={["critical","high","medium","low"].map((v) => ({ value: v, label: v }))} />
<div className="md:col-span-2"><TextArea label="Description" value={description} onChange={setDescription} placeholder="Describe the task" rows={5} /></div>
@@ -42,7 +42,7 @@ export function TaskInspector({ task, tasks, runs, agents, goals, projects, work
{actionError ? <InlineNotice message={actionError} /> : null}
{!actionError && actionNotice ? <InlineNotice message={actionNotice} tone="success" /> : null}
{editing ? (
<div className="grid gap-3 md:grid-cols-2">
<div className="grid min-w-0 gap-3 md:grid-cols-2">
<div className="md:col-span-2"><Input label={t("exec.title")} value={editTitle} onChange={setEditTitle} placeholder={t("exec.taskTitle")} /></div>
<div className="md:col-span-2"><TextArea label={t("exec.description")} value={editDescription} onChange={setEditDescription} placeholder={t("exec.description")} rows={4} /></div>
<Select label={t("exec.priority")} value={editPriority} onChange={v => setEditPriority(v as TaskRecord["priority"])} options={["critical","high","medium","low"].map(v => ({ value: v, label: v }))} />