This commit is contained in:
ZacharyZhang-NY committed 2026-03-19 16:51:44 -04:00
1 parent f482f25856
commit d126c4cf21
165 files changed
+123 -541297

No files matched your search

+113 -1
View File
@@ -5,11 +5,13 @@ import {
CheckCircle,
Cpu,
FolderOpen,
GearSix,
GlobeSimple,
Lightning,
Rocket,
Target,
WarningCircle,
X,
XCircle,
} from "@phosphor-icons/react";
import { getConnectorExecutionReadinessIssue, isConnectorExecutionReady } from "@shared/connector-policy";
@@ -50,6 +52,11 @@ export function CompanyOnboarding({
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [launchSummary, setLaunchSummary] = useState<OnboardingBootstrapResult | null>(null);
const [claudeSettingsOpen, setClaudeSettingsOpen] = useState(false);
const [claudeApiKey, setClaudeApiKey] = useState("");
const [claudeBaseUrl, setClaudeBaseUrl] = useState("");
const [claudeModel, setClaudeModel] = useState("");
const [claudeSettingsSaving, setClaudeSettingsSaving] = useState(false);
const executionReadyConnectors = useMemo(
() => connectors.filter((connector) => isConnectorExecutionReady(connector)),
@@ -70,6 +77,44 @@ export function CompanyOnboarding({
}
}, [executionReadyConnectors, selectedConnectorId]);
const openClaudeSettings = useCallback(() => {
const claude = connectors.find((c) => c.id === "claude_local");
if (!claude) return;
const lines = claude.envBindingText.split(/\r?\n/);
setClaudeApiKey(lines.find((l) => l.startsWith("ANTHROPIC_API_KEY="))?.split("=").slice(1).join("=") ?? "");
setClaudeBaseUrl(lines.find((l) => l.startsWith("ANTHROPIC_BASE_URL="))?.split("=").slice(1).join("=") ?? "");
setClaudeModel(claude.model ?? "");
setClaudeSettingsOpen(true);
}, [connectors]);
const saveClaudeSettings = useCallback(async () => {
setClaudeSettingsSaving(true);
try {
const envLines: string[] = [];
if (claudeApiKey.trim()) envLines.push(`ANTHROPIC_API_KEY=${claudeApiKey.trim()}`);
if (claudeBaseUrl.trim()) envLines.push(`ANTHROPIC_BASE_URL=${claudeBaseUrl.trim()}`);
const claude = connectors.find((c) => c.id === "claude_local");
if (!claude) return;
const existingLines = claude.envBindingText.split(/\r?\n/).filter((l) => {
const key = l.split("=")[0]?.trim();
return key && key !== "ANTHROPIC_API_KEY" && key !== "ANTHROPIC_BASE_URL" && l.trim();
});
const newEnvText = [...envLines, ...existingLines].join("\n");
unwrap(await window.agentCompany.saveConnector({
id: "claude_local",
command: claude.command,
model: claudeModel.trim() || null,
envBindingText: newEnvText,
notes: claude.notes,
}));
setClaudeSettingsOpen(false);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setClaudeSettingsSaving(false);
}
}, [claudeApiKey, claudeBaseUrl, claudeModel, connectors]);
const stepLabels = [
{ label: t("onboarding.stepLabel1"), icon: Buildings },
{ label: t("onboarding.stepLabel2"), icon: Target },
@@ -221,7 +266,7 @@ export function CompanyOnboarding({
) : (
<WarningCircle size={16} weight="fill" className="shrink-0 text-[color:var(--warn)]" />
)}
<div className="min-w-0">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={isReady ? "font-medium text-[color:var(--text)]" : isChecking ? "text-[color:var(--text)]" : "text-[color:var(--muted)]"}>
{connector.label}
@@ -236,6 +281,16 @@ export function CompanyOnboarding({
</div>
) : null}
</div>
{connector.id === "claude_local" ? (
<button
type="button"
onClick={(e) => { e.stopPropagation(); openClaudeSettings(); }}
className="shrink-0 rounded-[6px] p-1.5 text-[color:var(--muted)] transition hover:bg-[color:var(--panel-soft)] hover:text-[color:var(--text)]"
title={currentLocale === "zh" ? "Claude Code 设置" : "Claude Code Settings"}
>
<GearSix size={14} />
</button>
) : null}
</div>
);
})}
@@ -569,6 +624,63 @@ export function CompanyOnboarding({
</AnimatePresence>
</div>
</div>
{claudeSettingsOpen ? (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[color:var(--bg)]/72 backdrop-blur-sm" onClick={() => setClaudeSettingsOpen(false)}>
<div className="w-full max-w-[420px] rounded-[12px] border border-[color:var(--line)] bg-[color:var(--panel)] p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
<div className="mb-5 flex items-center justify-between">
<h3 className="text-[16px] font-semibold text-[color:var(--text)]">Claude Code {currentLocale === "zh" ? "设置" : "Settings"}</h3>
<button type="button" onClick={() => setClaudeSettingsOpen(false)} className="rounded-[6px] p-1 text-[color:var(--muted)] transition hover:text-[color:var(--text)]"><X size={16} /></button>
</div>
<div className="space-y-4">
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">API Key</label>
<input
type="password"
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeApiKey}
onChange={(e) => setClaudeApiKey(e.target.value)}
placeholder="sk-ant-..."
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用订阅认证" : "Leave empty to use subscription auth"}</div>
</div>
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">Base URL</label>
<input
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeBaseUrl}
onChange={(e) => setClaudeBaseUrl(e.target.value)}
placeholder="https://api.anthropic.com"
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用默认端点" : "Leave empty for default endpoint"}</div>
</div>
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">Model</label>
<input
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeModel}
onChange={(e) => setClaudeModel(e.target.value)}
placeholder="claude-sonnet-4-20250514"
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用连接器默认模型" : "Leave empty for connector default"}</div>
</div>
</div>
<div className="mt-6 flex justify-end gap-3">
<button type="button" onClick={() => setClaudeSettingsOpen(false)} className="rounded-[8px] px-4 py-2 text-[13px] font-medium text-[color:var(--muted)] transition hover:text-[color:var(--text)]">
{currentLocale === "zh" ? "取消" : "Cancel"}
</button>
<button
type="button"
onClick={() => void saveClaudeSettings()}
disabled={claudeSettingsSaving}
className="rounded-[8px] bg-[color:var(--accent)] px-4 py-2 text-[13px] font-medium text-white transition hover:opacity-90 disabled:opacity-50"
>
{claudeSettingsSaving ? (currentLocale === "zh" ? "保存中..." : "Saving...") : (currentLocale === "zh" ? "保存" : "Save")}
</button>
</div>
</div>
</div>
) : null}
</div>
);
}
+2 -14
View File
@@ -19,24 +19,12 @@ function getAuthActionCopy(connector: ConnectorRecord) {
message: "Claude Code is installed, but the local CLI is not authenticated for this desktop runtime.",
buttonLabel: "Open Terminal To Sign In",
};
case "opencode_local":
return {
title: "OpenCode provider login required",
message: "OpenCode is installed, but the provider login must be completed before runs can start.",
buttonLabel: "Open Terminal To Sign In",
};
case "gemini_local":
return {
title: "Gemini authentication required",
message: "Gemini CLI is available, but the runtime still needs an authenticated session. AgentCompany will open Gemini in a terminal and you can complete its interactive auth flow there.",
buttonLabel: "Open Terminal",
};
case "kimi_local":
return {
title: "Kimi interactive login required",
message: "Kimi Code is available in generic mode. AgentCompany can open the CLI in a terminal so you can complete the interactive login flow there if prompted.",
buttonLabel: "Open Terminal",
};
}
}
@@ -55,8 +43,8 @@ export function ConnectorEditor({ connector, onSaved }: { connector: ConnectorRe
const authPollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const authActionCopy = getAuthActionCopy(connector);
const needsLogin = connector.authState === "required" || connector.status === "auth_required";
const requiresExplicitModel = connector.id === "opencode_local";
const shouldLoadModelOptions = connector.capabilityMatrix.modelDiscovery || connector.id === "opencode_local";
const requiresExplicitModel = false;
const shouldLoadModelOptions = connector.capabilityMatrix.modelDiscovery;
useEffect(() => {
setModel(connector.model ?? "");
-2
View File
@@ -1176,8 +1176,6 @@ const adapterLabels: Record<string, string> = {
claude_local: "Claude",
codex_local: "Codex",
gemini_local: "Gemini",
opencode_local: "OpenCode",
kimi_local: "Kimi",
};
export function OrgChart({