update
This commit is contained in:
1 parent
09971cabc2
commit
9145572422
163 files changed
+3348
-2874
No files matched your search
@@ -5,6 +5,7 @@ import type { AgentRecord, ConnectorRecord, WorkspaceRecord } from "@shared/type
|
||||
import { isConnectorExecutionReady } from "@shared/connector-policy";
|
||||
import { unwrap } from "../../lib/desktop";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { useT } from "../../i18n";
|
||||
|
||||
export function AgentCreator({
|
||||
companyId,
|
||||
@@ -66,27 +67,28 @@ export function AgentCreator({
|
||||
}
|
||||
}, [executionReadyConnectors, connectorId]);
|
||||
|
||||
const t = useT();
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-w-0 gap-4">
|
||||
<Input label="Agent name" value={name} onChange={setName} placeholder="Agent name" hint="Display name shown in org chart and messages" />
|
||||
<Input label="Role" value={role} onChange={setRole} placeholder="e.g. Engineer, PM, CEO" hint="Determines which tasks the agent can handle" />
|
||||
<Input label="Title" value={title} onChange={setTitle} placeholder="Title" hint="Job title shown under the name" />
|
||||
<Input label={t("org.agentName")} value={name} onChange={setName} placeholder={t("org.agentName")} hint="Display name shown in org chart and messages" />
|
||||
<Input label={t("agent.role")} value={role} onChange={setRole} placeholder="e.g. Engineer, PM, CEO" hint="Determines which tasks the agent can handle" />
|
||||
<Input label={t("agent.title")} value={title} onChange={setTitle} placeholder={t("agent.title")} hint="Job title shown under the name" />
|
||||
<Select
|
||||
label="Connector"
|
||||
label={t("org.connector")}
|
||||
value={connectorId}
|
||||
onChange={(value) => setConnectorId(value as ConnectorRecord["id"])}
|
||||
options={executionReadyConnectors.map((connector) => ({ value: connector.id, label: connector.id.startsWith("claude_compat") && connector.notes ? connector.notes : connector.label }))}
|
||||
/>
|
||||
{executionReadyConnectors.length === 0 ? (
|
||||
<p className="text-[12px] text-warning">
|
||||
No execution-ready connector is available. Fix connector health before requesting hires.
|
||||
{t("org.noConnectorAvailable")}
|
||||
</p>
|
||||
) : null}
|
||||
{availableModels.length > 0 ? (
|
||||
<Select
|
||||
label="Model"
|
||||
label={t("org.model")}
|
||||
value={model}
|
||||
onChange={setModel}
|
||||
options={availableModels.map((m) => ({ value: m.id, label: m.label }))}
|
||||
@@ -94,11 +96,11 @@ export function AgentCreator({
|
||||
) : null}
|
||||
{supportsEffort ? (
|
||||
<Select
|
||||
label="Thinking effort"
|
||||
label={t("org.thinkingEffort")}
|
||||
value={thinkingEffort}
|
||||
onChange={setThinkingEffort}
|
||||
options={[
|
||||
{ value: "", label: "Default" },
|
||||
{ value: "", label: t("org.default") },
|
||||
{ value: "low", label: "Low" },
|
||||
{ value: "medium", label: "Medium" },
|
||||
{ value: "high", label: "High" },
|
||||
@@ -107,32 +109,32 @@ export function AgentCreator({
|
||||
) : null}
|
||||
<button type="button" onClick={() => setShowAdvanced(!showAdvanced)} className="flex items-center gap-1.5 text-[12px] font-medium text-muted-foreground transition hover:text-foreground">
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" className={`transition-transform ${showAdvanced ? "rotate-90" : ""}`}><path d="M3 1l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" fill="none" /></svg>
|
||||
Advanced settings
|
||||
{t("org.advancedSettings")}
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{showAdvanced ? (
|
||||
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }} className="grid gap-4 overflow-hidden">
|
||||
<Select
|
||||
label="Workspace"
|
||||
label={t("agent.workspace")}
|
||||
value={workspaceId}
|
||||
onChange={setWorkspaceId}
|
||||
options={[
|
||||
{ value: "", label: "No workspace" },
|
||||
{ value: "", label: t("agent.noWorkspace") },
|
||||
...workspaces.map((workspace) => ({ value: workspace.id, label: workspace.name })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Reports to"
|
||||
label={t("agent.reportsTo")}
|
||||
value={reportsTo}
|
||||
onChange={setReportsTo}
|
||||
options={[
|
||||
{ value: "", label: "No manager" },
|
||||
{ value: "", label: t("agent.noManager") },
|
||||
...agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
<Input label="Monthly budget (USD)" value={budget} onChange={setBudget} placeholder="120" type="number" hint="Spending cap per month; agent pauses when exceeded" />
|
||||
<Input label={t("org.monthlyBudget")} value={budget} onChange={setBudget} placeholder="120" type="number" hint="Spending cap per month; agent pauses when exceeded" />
|
||||
<TextArea
|
||||
label="Capabilities"
|
||||
label={t("agent.capabilities")}
|
||||
value={capabilities}
|
||||
onChange={setCapabilities}
|
||||
placeholder="What this agent does"
|
||||
@@ -142,17 +144,17 @@ export function AgentCreator({
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
<p className="text-[12px] text-muted-foreground">
|
||||
New agents enter the hiring pipeline first. Approval activates the agent and keeps onboarding, budgets, and org history linked to the same record.
|
||||
{t("org.hireRequestNote")}
|
||||
</p>
|
||||
<ActionButton
|
||||
label="Submit hire request"
|
||||
label={t("org.submitHireRequest")}
|
||||
tone="accent"
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
setRequestError(null);
|
||||
if (!selectedConnector) {
|
||||
setRequestError("Select an execution-ready connector before requesting a hire.");
|
||||
setRequestError(t("org.selectConnectorFirst"));
|
||||
return;
|
||||
}
|
||||
const meta: Record<string, string> = {};
|
||||
@@ -180,7 +182,7 @@ export function AgentCreator({
|
||||
setRequestError(null);
|
||||
await onSaved();
|
||||
} catch (error) {
|
||||
setRequestError(error instanceof Error ? error.message : "Hire request failed.");
|
||||
setRequestError(error instanceof Error ? error.message : t("org.hireRequestFailed"));
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
@@ -89,15 +89,15 @@ export function AgentRoster({
|
||||
<div>
|
||||
<div className={`truncate text-[14px] font-semibold text-foreground`}>{agent.name}</div>
|
||||
<div className="truncate text-[11px] text-muted-foreground">
|
||||
{agent.role} · {agent.title || "No title"}
|
||||
{agent.role} · {agent.title || t("org.noTitle")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StatusPill status={agent.status} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-[11px] text-muted-foreground">
|
||||
<span>Connector: {connector?.id.startsWith("claude_compat") && connector.notes ? connector.notes : connector?.label ?? agent.connectorId}</span>
|
||||
{workspace && <span>Workspace: {workspace.name}</span>}
|
||||
<span>{t("org.connectorLabel")} {connector?.id.startsWith("claude_compat") && connector.notes ? connector.notes : connector?.label ?? agent.connectorId}</span>
|
||||
{workspace && <span>{t("org.workspaceLabel")} {workspace.name}</span>}
|
||||
</div>
|
||||
<div className="mt-1.5">
|
||||
<BudgetBar spent={agent.spentMonthlyUsd} budget={agent.budgetMonthlyUsd} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import { Target } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { useT } from "../../i18n";
|
||||
|
||||
export function GoalCreator({
|
||||
companyId,
|
||||
@@ -14,39 +15,40 @@ export function GoalCreator({
|
||||
goals: GoalRecord[];
|
||||
onSaved: () => Promise<void>;
|
||||
}) {
|
||||
const t = useT();
|
||||
const [title, setTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [ownerAgentId, setOwnerAgentId] = useState(agents[0]?.id ?? "");
|
||||
const [parentId, setParentId] = useState("");
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label="Goal title" value={title} onChange={setTitle} placeholder="Goal title" />
|
||||
<Input label={t("org.goalTitle")} value={title} onChange={setTitle} placeholder={t("org.goalTitle")} />
|
||||
<TextArea
|
||||
label="Success criteria"
|
||||
label={t("org.successCriteria")}
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder="Define the acceptance criteria"
|
||||
placeholder={t("org.successCriteria")}
|
||||
/>
|
||||
<Select
|
||||
label="Owner agent"
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: "No owner" },
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...agents.map((agent) => ({ value: agent.id, label: agent.name })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Parent goal"
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: "No parent goal" },
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...goals.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<ActionButton
|
||||
label="Create goal"
|
||||
label={t("org.createGoal")}
|
||||
tone="accent"
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Pencil, Trash } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { ConfirmDelete } from "./helpers";
|
||||
import { useT } from "../../i18n";
|
||||
|
||||
export function GoalEditor({
|
||||
goal,
|
||||
@@ -24,47 +25,48 @@ export function GoalEditor({
|
||||
const [status, setStatus] = useState<GoalRecord["status"]>(goal.status);
|
||||
const [parentId, setParentId] = useState(goal.parentId ?? "");
|
||||
const [ownerAgentId, setOwnerAgentId] = useState(goal.ownerAgentId ?? "");
|
||||
const t = useT();
|
||||
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
||||
|
||||
const parentCandidates = goals.filter((g) => g.id !== goal.id);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label="Goal title" value={title} onChange={setTitle} placeholder="Goal title" />
|
||||
<TextArea label="Description" value={description} onChange={setDescription} placeholder="Description" />
|
||||
<Input label={t("org.goalTitle")} value={title} onChange={setTitle} placeholder={t("org.goalTitle")} />
|
||||
<TextArea label={t("org.description")} value={description} onChange={setDescription} placeholder={t("org.description")} />
|
||||
<Select
|
||||
label="Status"
|
||||
label={t("org.status")}
|
||||
value={status}
|
||||
onChange={(v) => setStatus(v as GoalRecord["status"])}
|
||||
options={[
|
||||
{ value: "planned", label: "Planned" },
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "achieved", label: "Achieved" },
|
||||
{ value: "cancelled", label: "Cancelled" },
|
||||
{ value: "planned", label: t("org.statusPlanned") },
|
||||
{ value: "active", label: t("org.statusActive") },
|
||||
{ value: "achieved", label: t("org.statusAchieved") },
|
||||
{ value: "cancelled", label: t("org.statusCancelled") },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Parent goal"
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: "No parent goal" },
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...parentCandidates.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Owner agent"
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: "No owner" },
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<ActionButton
|
||||
label="Save changes"
|
||||
label={t("org.saveChanges")}
|
||||
tone="accent"
|
||||
icon={Pencil}
|
||||
onClick={() =>
|
||||
@@ -99,7 +101,7 @@ export function GoalEditor({
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
label="Delete"
|
||||
label={t("common.delete")}
|
||||
tone="danger"
|
||||
icon={Trash}
|
||||
onClick={() => setConfirmingDelete(true)}
|
||||
|
||||
@@ -45,15 +45,15 @@ export function ProjectList({
|
||||
<div className="mb-2 text-[13px] text-muted-foreground">{project.description}</div>
|
||||
<div className="flex flex-wrap gap-4 text-[11px] text-muted-foreground">
|
||||
<span>
|
||||
Goal: {goals.find((g) => g.id === project.goalId)?.title ?? "None"}
|
||||
{t("org.goalLabel")} {goals.find((g) => g.id === project.goalId)?.title ?? t("exec.noGoal")}
|
||||
</span>
|
||||
<span>
|
||||
Lead: {agents.find((a) => a.id === project.leadAgentId)?.name ?? "Unassigned"}
|
||||
{t("org.leadLabel")} {agents.find((a) => a.id === project.leadAgentId)?.name ?? t("exec.unassigned")}
|
||||
</span>
|
||||
{project.targetDate && <span>Target: {project.targetDate}</span>}
|
||||
{project.targetDate && <span>{t("org.targetLabel")} {project.targetDate}</span>}
|
||||
{wsCount > 0 && (
|
||||
<span>
|
||||
{wsCount} workspace{wsCount !== 1 ? "s" : ""}
|
||||
{wsCount !== 1 ? t("org.workspacesCount").replace("{0}", String(wsCount)) : t("org.workspaceCount").replace("{0}", String(wsCount))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user