mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(grok): first-class xAI/Grok routing mode (UI + backend)
The Routing-mode toggle had Anthropic / Ollama / Self-Hosted / Mix but no way
to route the whole org to Grok. Add it end to end:
- backend: apply_mode("grok") + _apply_grok (GLOBAL default -> grok-build-0.1) +
derive_mode "grok" detection; ApplyModeRequest/ModeResponse accept "grok".
- panel: a "Grok" routing-mode card (between Anthropic and Ollama, gated on the
xAI key) + flipToGrok; a Grok group in the per-agent mix dropdown +
catalogGrokOnly + a grok ProviderBadge variant; the mix-save key check and
the AI-routing description now cover Grok.
- tests: integration derive_mode/apply_mode "grok" cases (+ grok provider row
in the fixture).
Gated: ruff + mypy clean; panel typecheck + lint clean.
This commit is contained in:
@@ -39,6 +39,7 @@ import {
|
||||
Server,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { AssignmentScope, ModelProvider } from "@/types";
|
||||
@@ -175,6 +176,9 @@ export function AIRoutingCard() {
|
||||
const catalogOllamaOnly = catalog.filter(
|
||||
(c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.OLLAMA_CLOUD,
|
||||
);
|
||||
const catalogGrokOnly = catalog.filter(
|
||||
(c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.GROK,
|
||||
);
|
||||
const catalogAnthropicOnly = catalog.filter(
|
||||
(c: { provider_type: ModelProvider }) => c.provider_type === ModelProvider.ANTHROPIC,
|
||||
);
|
||||
@@ -190,6 +194,20 @@ export function AIRoutingCard() {
|
||||
}
|
||||
};
|
||||
|
||||
const flipToGrok = async () => {
|
||||
if (!hasGrokKey) {
|
||||
toast.error("Save the Grok (xAI) API key first");
|
||||
return;
|
||||
}
|
||||
if (!confirm("Switch every agent to Grok? Clears any overrides.")) return;
|
||||
try {
|
||||
await applyMode.mutateAsync({ mode: "grok" });
|
||||
toast.success("All agents now on Grok");
|
||||
} catch (e) {
|
||||
toast.error("Switch failed: " + errMsg(e));
|
||||
}
|
||||
};
|
||||
|
||||
const flipToOllama = async () => {
|
||||
if (!hasOllamaKey) {
|
||||
toast.error("Save an Ollama API key first");
|
||||
@@ -236,6 +254,18 @@ export function AIRoutingCard() {
|
||||
toast.error("Pick a model for at least one agent");
|
||||
return;
|
||||
}
|
||||
const needsGrok = Object.values(per_agent).some((m) =>
|
||||
catalog.find(
|
||||
(c: { model_name: string; provider_type: ModelProvider }) =>
|
||||
c.model_name === m && c.provider_type === ModelProvider.GROK,
|
||||
),
|
||||
);
|
||||
if (needsGrok && !hasGrokKey) {
|
||||
toast.error(
|
||||
"At least one agent is routed to a Grok model but no key is saved",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const needsKey = Object.values(per_agent).some((m) =>
|
||||
catalog.find(
|
||||
(c: { model_name: string; provider_type: ModelProvider }) =>
|
||||
@@ -274,9 +304,9 @@ export function AIRoutingCard() {
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Decide which model backs each agent. Anthropic uses the mounted
|
||||
<code className="px-1"> ~/.claude </code> auth; Ollama Cloud uses
|
||||
the API key you save below; Self-Hosted connects to any OpenAI-compatible
|
||||
endpoint you run locally.
|
||||
<code className="px-1"> ~/.claude </code> auth; Grok (xAI) and Ollama
|
||||
Cloud use the API keys you save below; Self-Hosted connects to any
|
||||
OpenAI-compatible endpoint you run locally.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
@@ -391,7 +421,7 @@ export function AIRoutingCard() {
|
||||
{/* -------- Mode toggle -------- */}
|
||||
<section className="space-y-3">
|
||||
<Label className="text-sm font-medium">Routing mode</Label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-2">
|
||||
<ModeButton
|
||||
icon={<ShieldCheck className="h-4 w-4" />}
|
||||
label="Anthropic"
|
||||
@@ -400,6 +430,18 @@ export function AIRoutingCard() {
|
||||
onClick={flipToAnthropic}
|
||||
disabled={applyMode.isPending}
|
||||
/>
|
||||
<ModeButton
|
||||
icon={<Zap className="h-4 w-4" />}
|
||||
label="Grok"
|
||||
description={
|
||||
hasGrokKey
|
||||
? "Every agent uses Grok (grok-build-0.1)."
|
||||
: "Save the Grok (xAI) key first."
|
||||
}
|
||||
active={currentMode === "grok"}
|
||||
onClick={flipToGrok}
|
||||
disabled={applyMode.isPending || !hasGrokKey}
|
||||
/>
|
||||
<ModeButton
|
||||
icon={<Sparkles className="h-4 w-4" />}
|
||||
label="Ollama"
|
||||
@@ -545,6 +587,23 @@ export function AIRoutingCard() {
|
||||
</SelectGroup>
|
||||
)}
|
||||
|
||||
{/* Grok (xAI) models */}
|
||||
{catalogGrokOnly.length > 0 && (
|
||||
<SelectGroup>
|
||||
<SelectLabel>
|
||||
<ProviderBadge variant="grok" />
|
||||
Grok (xAI)
|
||||
</SelectLabel>
|
||||
{catalogGrokOnly.map(
|
||||
(c: { model_name: string; display_name: string }) => (
|
||||
<SelectItem key={c.model_name} value={c.model_name}>
|
||||
{c.display_name}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectGroup>
|
||||
)}
|
||||
|
||||
{/* Ollama Cloud models */}
|
||||
{catalogOllamaOnly.length > 0 && (
|
||||
<SelectGroup>
|
||||
@@ -659,17 +718,19 @@ function errMsg(e: unknown): string {
|
||||
function ProviderBadge({
|
||||
variant,
|
||||
}: {
|
||||
variant: "anthropic" | "ollama" | "self-hosted";
|
||||
variant: "anthropic" | "grok" | "ollama" | "self-hosted";
|
||||
}) {
|
||||
const styles: Record<string, string> = {
|
||||
anthropic: "bg-blue-500/20 text-blue-700 dark:text-blue-400",
|
||||
ollama: "bg-violet-500/20 text-violet-700 dark:text-violet-400",
|
||||
"self-hosted": "bg-purple-500/20 text-purple-700 dark:text-purple-400",
|
||||
grok: "bg-teal-500/20 text-teal-700 dark:text-teal-400",
|
||||
};
|
||||
const labels: Record<string, string> = {
|
||||
anthropic: "A",
|
||||
ollama: "O",
|
||||
"self-hosted": "S",
|
||||
grok: "G",
|
||||
};
|
||||
return (
|
||||
<span
|
||||
|
||||
@@ -29,7 +29,12 @@ export interface ModelAssignment {
|
||||
model_name: string;
|
||||
}
|
||||
|
||||
export type RoutingMode = "anthropic" | "ollama" | "self_hosted" | "mix";
|
||||
export type RoutingMode =
|
||||
| "anthropic"
|
||||
| "grok"
|
||||
| "ollama"
|
||||
| "self_hosted"
|
||||
| "mix";
|
||||
|
||||
export interface ModeSnapshot {
|
||||
mode: RoutingMode;
|
||||
|
||||
Reference in New Issue
Block a user