feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
This commit is contained in:
@@ -8,10 +8,8 @@ import {
|
||||
ChevronDown,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
FolderPlus,
|
||||
KeyRound,
|
||||
RefreshCw,
|
||||
RotateCw,
|
||||
} from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -39,8 +37,6 @@ import {
|
||||
} from '@/lib/telemetry';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import { useProjectConfigStore } from '@/stores/project-config';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
@@ -71,22 +67,6 @@ export function Settings() {
|
||||
setTelemetryEnabled,
|
||||
} = useSettingsStore();
|
||||
|
||||
const opencodeStatus = useOpencodeStore((state) => state.status);
|
||||
const opencodeRuntimeConfigSummary = useOpencodeStore((state) => state.runtimeConfigSummary);
|
||||
const opencodeProjects = useOpencodeStore((state) => state.projects);
|
||||
const activeOpencodeProject = useOpencodeStore((state) => state.activeProject);
|
||||
const opencodeLoading = useOpencodeStore((state) => state.loading);
|
||||
const opencodeError = useOpencodeStore((state) => state.error);
|
||||
const refreshOpencodeStatus = useOpencodeStore((state) => state.refreshStatus);
|
||||
const restartOpencodeRuntime = useOpencodeStore((state) => state.restart);
|
||||
const loadOpencodeRuntimeConfigSummary = useOpencodeStore(
|
||||
(state) => state.loadRuntimeConfigSummary
|
||||
);
|
||||
const loadOpencodeProjects = useOpencodeStore((state) => state.loadProjects);
|
||||
const pickAndOpenOpencodeProject = useOpencodeStore((state) => state.pickAndOpenProject);
|
||||
const setActiveOpencodeProject = useOpencodeStore((state) => state.setActiveProject);
|
||||
const loadProjectConfig = useProjectConfigStore((state) => state.load);
|
||||
|
||||
const [proxyServerDraft, setProxyServerDraft] = useState('');
|
||||
const [proxyHttpServerDraft, setProxyHttpServerDraft] = useState('');
|
||||
const [proxyHttpsServerDraft, setProxyHttpsServerDraft] = useState('');
|
||||
@@ -103,7 +83,6 @@ export function Settings() {
|
||||
const [adminPassword, setAdminPassword] = useState('');
|
||||
const [adminPasswordError, setAdminPasswordError] = useState<string | null>(null);
|
||||
const [adminUnlocking, setAdminUnlocking] = useState(false);
|
||||
const [workspaceProjectError, setWorkspaceProjectError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setProxyEnabledDraft(proxyEnabled);
|
||||
@@ -144,20 +123,6 @@ export function Settings() {
|
||||
return unsubscribe;
|
||||
}, [devModeUnlocked]);
|
||||
|
||||
useEffect(() => {
|
||||
void Promise.allSettled([
|
||||
refreshOpencodeStatus(),
|
||||
]);
|
||||
}, [refreshOpencodeStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!devModeUnlocked) return;
|
||||
void Promise.allSettled([
|
||||
loadOpencodeRuntimeConfigSummary(),
|
||||
loadOpencodeProjects(),
|
||||
]);
|
||||
}, [devModeUnlocked, loadOpencodeProjects, loadOpencodeRuntimeConfigSummary]);
|
||||
|
||||
const proxySettingsDirty = useMemo(() => {
|
||||
return (
|
||||
proxyEnabledDraft !== proxyEnabled ||
|
||||
@@ -212,14 +177,6 @@ export function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshOpencodeSettings = () => {
|
||||
void Promise.allSettled([
|
||||
refreshOpencodeStatus(),
|
||||
loadOpencodeRuntimeConfigSummary(),
|
||||
loadOpencodeProjects(),
|
||||
]);
|
||||
};
|
||||
|
||||
const handleRevealAdditionalSettings = () => {
|
||||
if (showAdditionalSettings) {
|
||||
setShowAdditionalSettings(false);
|
||||
@@ -257,30 +214,6 @@ export function Settings() {
|
||||
setShowAdditionalSettings(false);
|
||||
};
|
||||
|
||||
const handleAddOpencodeProject = async () => {
|
||||
const project = await pickAndOpenOpencodeProject();
|
||||
if (!project) return;
|
||||
await activateVerifiedProject(project.id);
|
||||
};
|
||||
|
||||
const activateVerifiedProject = async (projectId: string) => {
|
||||
try {
|
||||
setWorkspaceProjectError(null);
|
||||
const result = await loadProjectConfig(projectId);
|
||||
if (result.status === 'valid') {
|
||||
await setActiveOpencodeProject(projectId);
|
||||
return;
|
||||
}
|
||||
if (result.status === 'missing') {
|
||||
setWorkspaceProjectError('Project configuration is missing. Create the project from the sidebar first.');
|
||||
return;
|
||||
}
|
||||
setWorkspaceProjectError(result.error ?? 'Project configuration is invalid.');
|
||||
} catch (error) {
|
||||
setWorkspaceProjectError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowLogs = async () => {
|
||||
try {
|
||||
const logs = await hostApiFetch<{ content: string }>('/api/logs?tailLines=100');
|
||||
@@ -359,48 +292,6 @@ export function Settings() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-5" data-testid="settings-runtime-summary">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">Runtime</h2>
|
||||
</div>
|
||||
<Badge variant={opencodeStatus.state === 'running' ? 'default' : 'secondary'}>
|
||||
{opencodeStatus.state}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 rounded-lg border bg-card p-4">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-6 gap-y-2 text-sm">
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Status</div>
|
||||
<div className="font-medium">{opencodeStatus.state}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Port</div>
|
||||
<div className="font-medium">{opencodeStatus.port}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => void restartOpencodeRuntime()}
|
||||
disabled={opencodeLoading}
|
||||
aria-label="Restart runtime"
|
||||
data-testid="settings-runtime-restart"
|
||||
>
|
||||
<RotateCw className={cn('mr-2 h-4 w-4', opencodeLoading && 'animate-spin')} />
|
||||
Restart runtime
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{(opencodeStatus.error || opencodeError) && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
{opencodeStatus.error ?? opencodeError}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<Separator />
|
||||
|
||||
<section className="space-y-5" data-testid="settings-updates">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">{t('updates.title')}</h2>
|
||||
@@ -470,133 +361,16 @@ export function Settings() {
|
||||
<DisclosureContent open innerClassName="space-y-8">
|
||||
<Separator />
|
||||
|
||||
<section className="space-y-5" data-testid="settings-opencode-workspace">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">Workspace</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure the local runtime and project workspace.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleRefreshOpencodeSettings}
|
||||
disabled={opencodeLoading}
|
||||
aria-label="Refresh runtime settings"
|
||||
>
|
||||
<RefreshCw className={cn('mr-2 h-4 w-4', opencodeLoading && 'animate-spin')} />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-4 rounded-lg border bg-card p-4"
|
||||
data-testid="settings-opencode-models"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<Label className="text-sm font-medium">Models</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Resolved from the runtime configuration.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => navigate('/models')}>
|
||||
Configure models
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Default model</div>
|
||||
<div className="break-all font-medium">
|
||||
{opencodeRuntimeConfigSummary?.model ?? 'No configured model'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">Small model</div>
|
||||
<div className="break-all font-medium">
|
||||
{opencodeRuntimeConfigSummary?.smallModel ?? 'No configured small model'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="text-muted-foreground">Providers</span>
|
||||
<span className="font-medium">
|
||||
{`${opencodeRuntimeConfigSummary?.providerCount ?? 0} providers`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 rounded-lg border bg-card p-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium">Projects</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Folders opened as project workspaces.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => void handleAddOpencodeProject()}
|
||||
disabled={opencodeLoading}
|
||||
aria-label="Add project"
|
||||
>
|
||||
<FolderPlus className="mr-2 h-4 w-4" />
|
||||
Add project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border bg-background/60 p-3">
|
||||
<div className="text-xs text-muted-foreground">Active project</div>
|
||||
<div className="mt-1 truncate text-sm font-medium">
|
||||
{activeOpencodeProject?.name ?? 'No project selected'}
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs text-muted-foreground">
|
||||
{activeOpencodeProject?.path ?? 'Add a project folder to start a session.'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workspaceProjectError && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
{workspaceProjectError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{opencodeProjects.length > 0 && (
|
||||
<div className="max-h-56 space-y-2 overflow-auto pr-1">
|
||||
{opencodeProjects.map((project) => {
|
||||
const active = activeOpencodeProject?.id === project.id;
|
||||
return (
|
||||
<div
|
||||
key={project.id}
|
||||
className="flex items-center justify-between gap-3 rounded-md border bg-background/50 px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-medium">{project.name}</div>
|
||||
<div className="truncate text-xs text-muted-foreground">
|
||||
{project.path}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant={active ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
disabled={active || opencodeLoading}
|
||||
onClick={() => void activateVerifiedProject(project.id)}
|
||||
aria-label={
|
||||
active
|
||||
? `Active project ${project.name}`
|
||||
: `Use project ${project.name}`
|
||||
}
|
||||
>
|
||||
{active ? 'Active' : 'Use'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<section className="space-y-4" data-testid="settings-models">
|
||||
<div>
|
||||
<h2 className="text-lg font-medium">Models</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure provider accounts and inspect local token usage.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={() => navigate('/models')}>
|
||||
Configure models
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
<Separator />
|
||||
|
||||
Reference in New Issue
Block a user