mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(panel): resolve undefined font vars + remove dead client code
- layout.tsx/globals.css: font-sans/font-mono pointed at undefined --font-geist-* vars (Geist-starter leftover); now resolve to the actually-loaded Inter via --font-inter. No visible change. - remove orphaned company-goals-card.tsx (unmounted; live charter UI is goals-tab.tsx) - remove dead streamApi transcription REST client (zero consumers) + its barrel export; AgentStreamViewer (a different, live websocket-based component) untouched.
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-sans: var(--font-inter);
|
||||
--font-mono: var(--font-inter);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Providers } from "@/components/providers";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "RoboCo Control Panel",
|
||||
@@ -17,7 +17,7 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<body className={`${inter.className} ${inter.variable}`}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
companyGoalsApi,
|
||||
type CompanyGoalsUpdate,
|
||||
} from "@/lib/api/company-goals";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Target, Save } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
function parseError(e: unknown): string {
|
||||
return e instanceof Error ? e.message : "parse error";
|
||||
}
|
||||
|
||||
export function CompanyGoalsCard() {
|
||||
const queryClient = useQueryClient();
|
||||
// null = "show the server value"; deriving the displayed value avoids syncing
|
||||
// query state into local state with an effect (react-hooks/set-state-in-effect).
|
||||
const [northStar, setNorthStar] = useState<string | null>(null);
|
||||
const [constraints, setConstraints] = useState<string | null>(null);
|
||||
const [objectives, setObjectives] = useState<string | null>(null);
|
||||
const [policy, setPolicy] = useState<string | null>(null);
|
||||
const [brandVoice, setBrandVoice] = useState<string | null>(null);
|
||||
|
||||
const { data: goals, isLoading } = useQuery({
|
||||
queryKey: ["company-goals"],
|
||||
queryFn: companyGoalsApi.get,
|
||||
});
|
||||
|
||||
const northStarVal = northStar ?? goals?.north_star ?? "";
|
||||
const constraintsVal = constraints ?? (goals?.constraints ?? []).join("\n");
|
||||
const objectivesVal =
|
||||
objectives ?? JSON.stringify(goals?.objectives ?? [], null, 2);
|
||||
const policyVal =
|
||||
policy ?? JSON.stringify(goals?.operating_policy ?? {}, null, 2);
|
||||
const brandVoiceVal = brandVoice ?? goals?.brand_voice ?? "";
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (update: CompanyGoalsUpdate) => companyGoalsApi.update(update),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["company-goals"] });
|
||||
setNorthStar(null);
|
||||
setConstraints(null);
|
||||
setObjectives(null);
|
||||
setPolicy(null);
|
||||
setBrandVoice(null);
|
||||
toast.success("Company charter updated");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Failed to save: ${parseError(error)}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
let parsedObjectives: Record<string, unknown>[];
|
||||
let parsedPolicy: Record<string, unknown>;
|
||||
try {
|
||||
parsedObjectives = JSON.parse(objectivesVal);
|
||||
if (!Array.isArray(parsedObjectives)) {
|
||||
throw new Error("must be a JSON array");
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`Objectives: invalid JSON — ${parseError(e)}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
parsedPolicy = JSON.parse(policyVal);
|
||||
if (
|
||||
typeof parsedPolicy !== "object" ||
|
||||
parsedPolicy === null ||
|
||||
Array.isArray(parsedPolicy)
|
||||
) {
|
||||
throw new Error("must be a JSON object");
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`Operating policy: invalid JSON — ${parseError(e)}`);
|
||||
return;
|
||||
}
|
||||
saveMutation.mutate({
|
||||
north_star: northStarVal,
|
||||
objectives: parsedObjectives,
|
||||
constraints: constraintsVal
|
||||
.split("\n")
|
||||
.map((c) => c.trim())
|
||||
.filter(Boolean),
|
||||
operating_policy: parsedPolicy,
|
||||
brand_voice: brandVoiceVal,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Target className="h-5 w-5" />
|
||||
Company Charter
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
The CEO-owned north star, objectives, constraints, operating policy,
|
||||
and brand voice. Injected into every agent's briefing so all
|
||||
work stays goal-aware.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="north-star">North star</Label>
|
||||
<Textarea
|
||||
id="north-star"
|
||||
rows={3}
|
||||
value={northStarVal}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setNorthStar(e.target.value)}
|
||||
placeholder="The long-term vision in one or two sentences..."
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="brand-voice">Brand voice</Label>
|
||||
<Textarea
|
||||
id="brand-voice"
|
||||
rows={3}
|
||||
value={brandVoiceVal}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setBrandVoice(e.target.value)}
|
||||
placeholder="Sample posts or a style description for the Head of Marketing's drafts..."
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="constraints">Constraints (one per line)</Label>
|
||||
<Textarea
|
||||
id="constraints"
|
||||
rows={3}
|
||||
value={constraintsVal}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setConstraints(e.target.value)}
|
||||
placeholder={"AGPL only\nNo external data egress"}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="objectives">Objectives (JSON array)</Label>
|
||||
<Textarea
|
||||
id="objectives"
|
||||
rows={6}
|
||||
value={objectivesVal}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setObjectives(e.target.value)}
|
||||
className="font-mono text-xs"
|
||||
placeholder='[{"metric": "NPS", "target": 50, "status": "active"}]'
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="policy">Operating policy (JSON object)</Label>
|
||||
<Textarea
|
||||
id="policy"
|
||||
rows={6}
|
||||
value={policyVal}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setPolicy(e.target.value)}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"autonomy_level": "assisted", "monthly_budget_cap": 500}'
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={saveMutation.isPending || isLoading}
|
||||
>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
{saveMutation.isPending ? "Saving..." : "Save charter"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export { productsApi } from "./products";
|
||||
export { workSessionsApi } from "./work-sessions";
|
||||
export { gitApi } from "./git";
|
||||
export { a2aApi } from "./a2a";
|
||||
export { streamApi } from "./stream";
|
||||
export { settingsApi } from "./settings";
|
||||
export { companyGoalsApi } from "./company-goals";
|
||||
export { releaseApi } from "./release";
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* Stream API Client
|
||||
*
|
||||
* API functions for transcription and extraction operations.
|
||||
*/
|
||||
|
||||
import api from "./client";
|
||||
import { isMockMode } from "@/lib/mock-data";
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
// =============================================================================
|
||||
|
||||
export interface StreamChunkRequest {
|
||||
channel_name: string;
|
||||
audio_data: string; // Base64 encoded
|
||||
chunk_index: number;
|
||||
is_final?: boolean;
|
||||
}
|
||||
|
||||
export interface StreamChunkResponse {
|
||||
status: string;
|
||||
chunk_index: number;
|
||||
queued: boolean;
|
||||
}
|
||||
|
||||
export interface StreamCompleteRequest {
|
||||
channel_name: string;
|
||||
session_id?: string;
|
||||
}
|
||||
|
||||
export interface StreamCompleteResponse {
|
||||
status: string;
|
||||
channel_name: string;
|
||||
total_chunks: number;
|
||||
transcription_id?: string;
|
||||
}
|
||||
|
||||
export interface ExtractionRequest {
|
||||
content: string;
|
||||
extraction_type: "tasks" | "decisions" | "action_items" | "summary";
|
||||
context?: string;
|
||||
}
|
||||
|
||||
export interface ExtractionResponse {
|
||||
extraction_type: string;
|
||||
results: Record<string, unknown>[];
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
export interface TranscriptionStats {
|
||||
total_transcriptions: number;
|
||||
active_channels: number;
|
||||
chunks_processed: number;
|
||||
avg_processing_time_ms: number;
|
||||
}
|
||||
|
||||
export interface ChannelPermissions {
|
||||
channel_name: string;
|
||||
can_transcribe: boolean;
|
||||
can_extract: boolean;
|
||||
rate_limit?: number;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// API Client
|
||||
// =============================================================================
|
||||
|
||||
export const streamApi = {
|
||||
// ===========================================================================
|
||||
// TRANSCRIPTION ENDPOINTS
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Submit an audio chunk for transcription
|
||||
*/
|
||||
submitChunk: async (
|
||||
request: StreamChunkRequest,
|
||||
): Promise<StreamChunkResponse> => {
|
||||
if (isMockMode()) {
|
||||
return {
|
||||
status: "queued",
|
||||
chunk_index: request.chunk_index,
|
||||
queued: true,
|
||||
};
|
||||
}
|
||||
const { data } = await api.post<StreamChunkResponse>(
|
||||
"/stream/chunk",
|
||||
request,
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Complete a transcription session
|
||||
*/
|
||||
complete: async (
|
||||
request: StreamCompleteRequest,
|
||||
): Promise<StreamCompleteResponse> => {
|
||||
if (isMockMode()) {
|
||||
return {
|
||||
status: "completed",
|
||||
channel_name: request.channel_name,
|
||||
total_chunks: 10,
|
||||
transcription_id: `trans-${Date.now()}`,
|
||||
};
|
||||
}
|
||||
const { data } = await api.post<StreamCompleteResponse>(
|
||||
"/stream/complete",
|
||||
request,
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
// ===========================================================================
|
||||
// EXTRACTION ENDPOINTS
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Extract structured information from content
|
||||
*/
|
||||
extract: async (request: ExtractionRequest): Promise<ExtractionResponse> => {
|
||||
if (isMockMode()) {
|
||||
return {
|
||||
extraction_type: request.extraction_type,
|
||||
results: [],
|
||||
confidence: 0.85,
|
||||
};
|
||||
}
|
||||
const { data } = await api.post<ExtractionResponse>(
|
||||
"/stream/extract",
|
||||
request,
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
// ===========================================================================
|
||||
// STATS & PERMISSIONS
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Get transcription statistics
|
||||
*/
|
||||
getStats: async (): Promise<TranscriptionStats> => {
|
||||
if (isMockMode()) {
|
||||
return {
|
||||
total_transcriptions: 100,
|
||||
active_channels: 5,
|
||||
chunks_processed: 1500,
|
||||
avg_processing_time_ms: 250,
|
||||
};
|
||||
}
|
||||
const { data } = await api.get<TranscriptionStats>("/stream/stats");
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all channel permissions
|
||||
*/
|
||||
getPermissions: async (): Promise<ChannelPermissions[]> => {
|
||||
if (isMockMode()) {
|
||||
return [];
|
||||
}
|
||||
const { data } = await api.get<ChannelPermissions[]>("/stream/permissions");
|
||||
return data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user