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:
@@ -1,131 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
CircleDot,
|
||||
Play,
|
||||
RefreshCw,
|
||||
ServerCog,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function statusClass(state: string): string {
|
||||
if (state === 'running') {
|
||||
return 'border-emerald-500/20 bg-emerald-500/10 text-emerald-700';
|
||||
}
|
||||
if (state === 'error') {
|
||||
return 'border-red-500/20 bg-red-500/10 text-red-700';
|
||||
}
|
||||
if (state === 'starting') {
|
||||
return 'border-amber-500/20 bg-amber-500/10 text-amber-700';
|
||||
}
|
||||
return 'border-border bg-surface-subtle text-muted-foreground';
|
||||
}
|
||||
|
||||
export function OpencodeRuntimePanel() {
|
||||
const { t } = useTranslation('dashboard');
|
||||
const status = useOpencodeStore((state) => state.status);
|
||||
const summary = useOpencodeStore((state) => state.runtimeConfigSummary);
|
||||
const loading = useOpencodeStore((state) => state.loading);
|
||||
const error = useOpencodeStore((state) => state.error);
|
||||
const refreshStatus = useOpencodeStore((state) => state.refreshStatus);
|
||||
const loadRuntimeConfigSummary = useOpencodeStore((state) => state.loadRuntimeConfigSummary);
|
||||
const restart = useOpencodeStore((state) => state.restart);
|
||||
const start = useOpencodeStore((state) => state.start);
|
||||
|
||||
useEffect(() => {
|
||||
void refreshStatus();
|
||||
void loadRuntimeConfigSummary();
|
||||
}, [loadRuntimeConfigSummary, refreshStatus]);
|
||||
|
||||
const isStopped = status.state === 'stopped';
|
||||
const isBusy = loading || status.state === 'starting';
|
||||
|
||||
const handleRuntimeAction = async () => {
|
||||
if (isStopped) {
|
||||
await start();
|
||||
} else {
|
||||
await restart();
|
||||
}
|
||||
await refreshStatus();
|
||||
await loadRuntimeConfigSummary();
|
||||
};
|
||||
|
||||
return (
|
||||
<section data-testid="opencode-runtime-panel" className="space-y-5">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<ServerCog className="h-5 w-5 text-foreground/70" aria-hidden="true" />
|
||||
<h2 className="text-2xl font-semibold tracking-[-0.03em] text-foreground">
|
||||
{t('opencodeRuntime.title', 'Makelore runtime')}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => void handleRuntimeAction()}
|
||||
disabled={isBusy}
|
||||
aria-label={isStopped
|
||||
? t('opencodeRuntime.start', 'Start runtime')
|
||||
: t('opencodeRuntime.restart', 'Restart runtime')}
|
||||
className="h-10 rounded-xl border-border bg-transparent px-4 hover:bg-surface-subtle"
|
||||
>
|
||||
{isStopped ? (
|
||||
<Play className="mr-2 h-4 w-4" aria-hidden="true" />
|
||||
) : (
|
||||
<RefreshCw className={cn('mr-2 h-4 w-4', isBusy && 'animate-spin')} aria-hidden="true" />
|
||||
)}
|
||||
{isStopped
|
||||
? t('opencodeRuntime.start', 'Start runtime')
|
||||
: t('opencodeRuntime.restart', 'Restart runtime')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
|
||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
{t('opencodeRuntime.status', 'Status')}
|
||||
</p>
|
||||
<div className={cn('mt-3 inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-semibold', statusClass(status.state))}>
|
||||
<CircleDot className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
{status.state}
|
||||
</div>
|
||||
</div>
|
||||
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
|
||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
{t('opencodeRuntime.port', 'Port')}
|
||||
</p>
|
||||
<p className="mt-3 font-mono text-lg font-semibold text-foreground">
|
||||
{status.port}
|
||||
</p>
|
||||
</div>
|
||||
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
|
||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
{t('opencodeRuntime.providers', 'Providers')}
|
||||
</p>
|
||||
<p className="mt-3 font-mono text-lg font-semibold text-foreground">
|
||||
{summary?.providerCount ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="surface-card rounded-2xl border border-border/70 bg-background p-4 shadow-none">
|
||||
<p className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
{t('opencodeRuntime.defaultModel', 'Default model')}
|
||||
</p>
|
||||
<p className="mt-2 break-all font-mono text-sm font-semibold text-foreground">
|
||||
{summary?.model || t('opencodeRuntime.noModel', 'No configured model')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p className="rounded-xl border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm font-medium text-red-700">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -6,13 +6,11 @@ import {
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import { trackUiEvent } from '@/lib/telemetry';
|
||||
import { ProvidersSettings } from '@/components/settings/ProvidersSettings';
|
||||
import { FeedbackState } from '@/components/common/FeedbackState';
|
||||
import { OpencodeRuntimePanel } from './OpencodeRuntimePanel';
|
||||
import {
|
||||
filterUsageHistoryByWindow,
|
||||
groupUsageHistory,
|
||||
@@ -37,9 +35,7 @@ function isHiddenUsageSource(source?: string): boolean {
|
||||
|
||||
export function Models() {
|
||||
const { t } = useTranslation(['dashboard', 'settings']);
|
||||
const opencodeStatus = useOpencodeStore((state) => state.status);
|
||||
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
|
||||
const runtimeRunning = opencodeStatus.state === 'running';
|
||||
const usageFetchMaxAttempts = window.electron.platform === 'win32'
|
||||
? WINDOWS_USAGE_FETCH_MAX_ATTEMPTS
|
||||
: DEFAULT_USAGE_FETCH_MAX_ATTEMPTS;
|
||||
@@ -112,10 +108,6 @@ export function Models() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!runtimeRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestRefresh = () => {
|
||||
if (usageFetchStatusRef.current === 'loading') return;
|
||||
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
|
||||
@@ -140,7 +132,7 @@ export function Models() {
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
};
|
||||
}, [runtimeRunning]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (usageFetchTimerRef.current) {
|
||||
@@ -148,18 +140,11 @@ export function Models() {
|
||||
usageFetchTimerRef.current = null;
|
||||
}
|
||||
|
||||
if (!runtimeRunning) {
|
||||
dispatchFetch({ type: 'reset' });
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchFetch({ type: 'start' });
|
||||
const generation = usageFetchGenerationRef.current + 1;
|
||||
usageFetchGenerationRef.current = generation;
|
||||
const restartMarker = `${opencodeStatus.pid ?? 'na'}:${opencodeStatus.startedAt ?? 'na'}`;
|
||||
trackUiEvent('models.token_usage_fetch_started', {
|
||||
generation,
|
||||
restartMarker,
|
||||
});
|
||||
|
||||
// Safety timeout: if the fetch cycle hasn't resolved after 30 s,
|
||||
@@ -168,7 +153,6 @@ export function Models() {
|
||||
if (usageFetchGenerationRef.current !== generation) return;
|
||||
trackUiEvent('models.token_usage_fetch_safety_timeout', {
|
||||
generation,
|
||||
restartMarker,
|
||||
});
|
||||
dispatchFetch({ type: 'failed' });
|
||||
}, 30_000);
|
||||
@@ -177,7 +161,6 @@ export function Models() {
|
||||
trackUiEvent('models.token_usage_fetch_attempt', {
|
||||
generation,
|
||||
attempt,
|
||||
restartMarker,
|
||||
});
|
||||
try {
|
||||
const entries = await hostApiFetch<UsageHistoryEntry[]>('/api/usage/recent-token-history');
|
||||
@@ -189,7 +172,6 @@ export function Models() {
|
||||
generation,
|
||||
attempt,
|
||||
records: normalized.length,
|
||||
restartMarker,
|
||||
});
|
||||
|
||||
if (normalized.length === 0 && attempt < usageFetchMaxAttempts) {
|
||||
@@ -197,7 +179,6 @@ export function Models() {
|
||||
generation,
|
||||
attempt,
|
||||
reason: 'empty',
|
||||
restartMarker,
|
||||
});
|
||||
usageFetchTimerRef.current = setTimeout(() => {
|
||||
void fetchUsageHistoryWithRetry(attempt + 1);
|
||||
@@ -208,7 +189,6 @@ export function Models() {
|
||||
generation,
|
||||
attempt,
|
||||
reason: 'empty',
|
||||
restartMarker,
|
||||
});
|
||||
}
|
||||
dispatchFetch({ type: 'done', data: normalized });
|
||||
@@ -218,7 +198,6 @@ export function Models() {
|
||||
trackUiEvent('models.token_usage_fetch_failed_attempt', {
|
||||
generation,
|
||||
attempt,
|
||||
restartMarker,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
if (attempt < usageFetchMaxAttempts) {
|
||||
@@ -226,7 +205,6 @@ export function Models() {
|
||||
generation,
|
||||
attempt,
|
||||
reason: 'error',
|
||||
restartMarker,
|
||||
});
|
||||
usageFetchTimerRef.current = setTimeout(() => {
|
||||
void fetchUsageHistoryWithRetry(attempt + 1);
|
||||
@@ -238,7 +216,6 @@ export function Models() {
|
||||
generation,
|
||||
attempt,
|
||||
reason: 'error',
|
||||
restartMarker,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -252,16 +229,12 @@ export function Models() {
|
||||
usageFetchTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [runtimeRunning, opencodeStatus.startedAt, opencodeStatus.pid, usageFetchMaxAttempts, usageRefreshNonce]);
|
||||
}, [usageFetchMaxAttempts, usageRefreshNonce]);
|
||||
|
||||
const usageHistory = runtimeRunning
|
||||
? fetchState.data.filter((entry) => !shouldHideUsageEntry(entry))
|
||||
: [];
|
||||
const stableUsageHistory = runtimeRunning
|
||||
? fetchState.stableData.filter((entry) => !shouldHideUsageEntry(entry))
|
||||
: [];
|
||||
const usageHistory = fetchState.data.filter((entry) => !shouldHideUsageEntry(entry));
|
||||
const stableUsageHistory = fetchState.stableData.filter((entry) => !shouldHideUsageEntry(entry));
|
||||
const visibleUsageHistory = resolveVisibleUsageHistory(usageHistory, stableUsageHistory, {
|
||||
preferStableOnEmpty: runtimeRunning && fetchState.status === 'loading',
|
||||
preferStableOnEmpty: fetchState.status === 'loading',
|
||||
});
|
||||
const filteredUsageHistory = filterUsageHistoryByWindow(visibleUsageHistory, usageWindow);
|
||||
const usageGroups = groupUsageHistory(filteredUsageHistory, usageGroupBy);
|
||||
@@ -269,8 +242,8 @@ export function Models() {
|
||||
const usageTotalPages = Math.max(1, Math.ceil(filteredUsageHistory.length / usagePageSize));
|
||||
const safeUsagePage = Math.min(usagePage, usageTotalPages);
|
||||
const pagedUsageHistory = filteredUsageHistory.slice((safeUsagePage - 1) * usagePageSize, safeUsagePage * usagePageSize);
|
||||
const usageLoading = runtimeRunning && fetchState.status === 'loading' && visibleUsageHistory.length === 0;
|
||||
const usageRefreshing = runtimeRunning && fetchState.status === 'loading' && visibleUsageHistory.length > 0;
|
||||
const usageLoading = fetchState.status === 'loading' && visibleUsageHistory.length === 0;
|
||||
const usageRefreshing = fetchState.status === 'loading' && visibleUsageHistory.length > 0;
|
||||
|
||||
return (
|
||||
<div data-testid="models-page" className="flex h-[calc(100vh-2.5rem)] flex-col -m-6 overflow-hidden bg-background">
|
||||
@@ -290,8 +263,6 @@ export function Models() {
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="-mr-2 min-h-0 flex-1 space-y-10 overflow-y-auto pb-10 pr-2">
|
||||
<OpencodeRuntimePanel />
|
||||
|
||||
{/* AI Providers Section */}
|
||||
<ProvidersSettings />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user