diff --git a/ui/src/components/profiles/openrouter-quick-start.tsx b/ui/src/components/profiles/openrouter-quick-start.tsx
index 9fc4e746..4e273f6e 100644
--- a/ui/src/components/profiles/openrouter-quick-start.tsx
+++ b/ui/src/components/profiles/openrouter-quick-start.tsx
@@ -3,12 +3,16 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { useOpenRouterReady } from '@/hooks/use-openrouter-models';
+import { useLocalRuntimeReadiness } from '@/hooks/use-local-runtime-readiness';
import { cn } from '@/lib/utils';
import {
ArrowRight,
+ BrainCircuit,
CloudCog,
ExternalLink,
+ HardDriveDownload,
KeyRound,
+ Laptop,
SlidersHorizontal,
Sparkles,
Zap,
@@ -22,6 +26,8 @@ interface OpenRouterQuickStartProps {
onAlibabaCodingPlanClick: () => void;
onCliproxyClick: () => void;
onCustomClick: () => void;
+ onOllamaClick: () => void;
+ onLlamacppClick: () => void;
}
interface QuickStartCardProps {
@@ -38,6 +44,24 @@ interface QuickStartCardProps {
footer?: ReactNode;
}
+type LocalRuntimeStatus = 'ready' | 'missing-model' | 'offline';
+
+type LocalRuntimeView = {
+ id: 'ollama' | 'llamacpp';
+ endpoint: string;
+ status: LocalRuntimeStatus;
+ commandHint: string;
+ detectedModelCount: number;
+};
+
+type LocalRuntimeCardState = {
+ badge: string;
+ badgeClassName: string;
+ actionLabel: string;
+ description: string;
+ footer: string;
+};
+
function QuickStartCard({
badge,
badgeClassName,
@@ -84,6 +108,52 @@ function QuickStartCard({
);
}
+function getLocalRuntimeCardState(
+ runtime: LocalRuntimeView | undefined,
+ label: string
+): LocalRuntimeCardState {
+ if (!runtime) {
+ return {
+ badge: 'Checking',
+ badgeClassName: 'bg-muted text-muted-foreground',
+ actionLabel: `Set up ${label}`,
+ description: 'Checking the local runtime status before showing setup guidance.',
+ footer: 'Checking the local runtime...',
+ };
+ }
+
+ if (runtime.status === 'ready') {
+ return {
+ badge: 'Ready',
+ badgeClassName:
+ 'bg-emerald-500/10 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-200',
+ actionLabel: `Use ${label}`,
+ description: 'Best for private prompts, offline workflows, and cheaper batch transforms.',
+ footer: `Endpoint ready at ${runtime.endpoint}`,
+ };
+ }
+
+ if (runtime.status === 'missing-model') {
+ return {
+ badge: 'Needs model',
+ badgeClassName: 'bg-amber-500/10 text-amber-700 dark:bg-amber-500/20 dark:text-amber-200',
+ actionLabel: `Finish ${label} setup`,
+ description:
+ 'The runtime is up, but the recommended local model still needs to be pulled or loaded.',
+ footer: `Run \`${runtime.commandHint}\` to finish local setup.`,
+ };
+ }
+
+ return {
+ badge: 'Offline',
+ badgeClassName: 'bg-muted text-muted-foreground',
+ actionLabel: `Set up ${label}`,
+ description:
+ 'Use this lane for privacy-sensitive work, cheap local transforms, and offline sessions.',
+ footer: `Run \`${runtime.commandHint}\` to bring the local endpoint online.`,
+ };
+}
+
export function OpenRouterQuickStart({
hasProfiles,
profileCount,
@@ -91,31 +161,37 @@ export function OpenRouterQuickStart({
onAlibabaCodingPlanClick,
onCliproxyClick,
onCustomClick,
+ onOllamaClick,
+ onLlamacppClick,
}: OpenRouterQuickStartProps) {
const { t } = useTranslation();
const { modelCount, isLoading } = useOpenRouterReady();
+ const { data: localRuntimeData } = useLocalRuntimeReadiness();
const modelCountLabel = isLoading ? '300+' : `${modelCount}+`;
const profileSummaryLabel = hasProfiles
- ? t('openrouterQuickStart.profileCount', { count: profileCount })
- : t('openrouterQuickStart.recommended');
+ ? `${profileCount} ${profileCount === 1 ? 'profile' : 'profiles'}`
+ : 'Premium quality + local control';
const summaryTitle = hasProfiles
- ? t('openrouterQuickStart.selectProfileTitle')
- : t('apiProfiles.noProfilesYet');
+ ? 'Choose a profile or add another lane'
+ : 'Choose your first profile lane';
const summaryDescription = hasProfiles
- ? t('openrouterQuickStart.summaryDescriptionWithProfiles', { count: profileCount })
- : t('openrouterQuickStart.summaryDescriptionNoProfiles');
+ ? `You already have ${profileCount} profile${profileCount === 1 ? '' : 's'} in this workspace. Keep premium-quality providers for serious coding and add local runtimes when privacy, cost, or offline work matters.`
+ : 'Pick the lane that matches the work. Premium providers stay the default for reliable coding. Local runtimes are best for privacy, cheaper transforms, and experimentation.';
+ const ollamaRuntime = localRuntimeData?.runtimes.find((runtime) => runtime.id === 'ollama');
+ const llamacppRuntime = localRuntimeData?.runtimes.find((runtime) => runtime.id === 'llamacpp');
+ const ollamaCardState = getLocalRuntimeCardState(ollamaRuntime, 'Ollama');
+ const llamacppCardState = getLocalRuntimeCardState(llamacppRuntime, 'llama.cpp');
return (
-
+
{profileSummaryLabel}
-
- {t('openrouterQuickStart.openrouterModelsBadge', { modelCountLabel })}
-
+ Best quality by default
+ Local lane when task fit wins
{summaryTitle}
@@ -130,50 +206,171 @@ export function OpenRouterQuickStart({
-