ai_api / src /app /(dashboard) /dashboard /context /rtk /RtkContextPageClient.tsx
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
14.5 kB
"use client";
import { useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { SegmentedControl, Collapsible } from "@/shared/components";
import RtkLearnDiscoverCard from "./RtkLearnDiscoverCard";
type RtkFilter = {
id: string;
name: string;
description: string;
commandTypes: string[];
category: string;
priority: number;
};
type RtkConfig = {
enabled: boolean;
intensity: "minimal" | "standard" | "aggressive";
applyToToolResults: boolean;
applyToAssistantMessages: boolean;
applyToCodeBlocks: boolean;
enabledFilters: string[];
disabledFilters: string[];
maxLinesPerResult: number;
maxCharsPerResult: number;
deduplicateThreshold: number;
customFiltersEnabled: boolean;
trustProjectFilters: boolean;
rawOutputRetention: "never" | "failures" | "always";
rawOutputMaxBytes: number;
};
type AnalyticsSummary = {
totalRequests: number;
totalTokensSaved: number;
avgSavingsPct: number;
byEngine?: Record<string, { count: number; tokensSaved: number; avgSavingsPct: number }>;
};
type PreviewResult = {
text?: string;
compressed?: boolean;
originalTokens?: number;
compressedTokens?: number;
techniquesUsed?: string[];
detection?: { type: string; confidence: number; category: string };
error?: string;
};
const SAMPLE_OUTPUT = `$ npm run typecheck
src/lib/example.ts:10:15 - error TS2322: Type 'string' is not assignable to type 'number'.
10 const value: number = "bad";
~~~~~
Found 1 error in src/lib/example.ts:10`;
function formatNumber(value: number | undefined): string {
return new Intl.NumberFormat().format(value ?? 0);
}
export default function RtkContextPageClient() {
const t = useTranslations("contextRtk");
const [filters, setFilters] = useState<RtkFilter[]>([]);
const [config, setConfig] = useState<RtkConfig | null>(null);
const [analytics, setAnalytics] = useState<AnalyticsSummary | null>(null);
const [sample, setSample] = useState(SAMPLE_OUTPUT);
const [preview, setPreview] = useState<PreviewResult | null>(null);
const [saving, setSaving] = useState(false);
const [viewMode, setViewMode] = useState<"simple" | "advanced">("simple");
const [masterEnabled, setMasterEnabled] = useState<boolean | null>(null);
useEffect(() => {
fetch("/api/settings/compression")
.then((res) => (res.ok ? res.json() : null))
.then((data) => setMasterEnabled(data?.enabled ?? false))
.catch(() => {});
}, []);
useEffect(() => {
fetch("/api/context/rtk/filters")
.then((res) => (res.ok ? res.json() : null))
.then((data) => setFilters(Array.isArray(data?.filters) ? data.filters : []))
.catch(() => {});
fetch("/api/context/rtk/config")
.then((res) => (res.ok ? res.json() : null))
.then((data) => setConfig(data))
.catch(() => {});
fetch("/api/context/analytics?since=7d")
.then((res) => (res.ok ? res.json() : null))
.then((data) => setAnalytics(data))
.catch(() => {});
}, []);
const groupedFilters = useMemo(() => {
return filters.reduce<Record<string, RtkFilter[]>>((groups, filter) => {
groups[filter.category] = [...(groups[filter.category] ?? []), filter];
return groups;
}, {});
}, [filters]);
const activeFilterCount = useMemo(() => {
if (!config) return 0;
if (config.enabledFilters.length > 0) return config.enabledFilters.length;
return filters.filter((filter) => !config.disabledFilters.includes(filter.id)).length;
}, [config, filters]);
const saveConfig = async (patch: Partial<RtkConfig>) => {
if (!config) return;
const nextConfig = { ...config, ...patch };
setConfig(nextConfig);
setSaving(true);
try {
const res = await fetch("/api/context/rtk/config", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
});
if (res.ok) setConfig(await res.json());
} finally {
setSaving(false);
}
};
const toggleFilter = (filterId: string, enabled: boolean) => {
if (!config) return;
const disabledFilters = enabled
? config.disabledFilters.filter((id) => id !== filterId)
: [...new Set([...config.disabledFilters, filterId])];
saveConfig({ disabledFilters });
};
const runPreview = async () => {
const res = await fetch("/api/context/rtk/test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: sample, config: config ?? undefined }),
});
setPreview(res.ok ? await res.json() : { error: await res.text() });
};
const rtkStats = analytics?.byEngine?.rtk;
const statCards = [
[t("tokensFiltered"), formatNumber(rtkStats?.tokensSaved ?? analytics?.totalTokensSaved)],
[t("filtersActive"), formatNumber(activeFilterCount)],
[t("requests"), formatNumber(rtkStats?.count ?? analytics?.totalRequests)],
[t("avgSavings"), `${rtkStats?.avgSavingsPct ?? analytics?.avgSavingsPct ?? 0}%`],
];
return (
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<header className="flex flex-col gap-2">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<span className="material-symbols-outlined text-[30px] text-primary">filter_alt</span>
<div>
<h1 className="text-2xl font-bold text-text-main">{t("title")}</h1>
<p className="text-sm text-text-muted">{t("description")}</p>
</div>
</div>
<SegmentedControl
value={viewMode}
onChange={(v) => setViewMode(v as "simple" | "advanced")}
options={[
{ value: "simple", label: t("simpleMode") || "Simple" },
{ value: "advanced", label: t("advancedMode") || "Advanced" },
]}
/>
</div>
</header>
<section className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
{statCards.map(([label, value]) => (
<div key={label} className="rounded-lg border border-border bg-surface p-4">
<p className="text-xs uppercase text-text-muted">{label}</p>
<p className="mt-1 text-xl font-semibold text-text-main">{value}</p>
</div>
))}
</section>
{masterEnabled === false && (
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-300 flex items-start gap-2">
<span className="material-symbols-outlined text-[18px]">info</span>
<p>{t("masterSwitchOffAlert")}</p>
</div>
)}
{config && (
<section className="rounded-lg border border-border bg-surface p-4">
{/* On/off + intensity now live in the panel (/dashboard/context/settings). This
page edits RTK's detailed configuration only. */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<label className="flex flex-col gap-1 text-sm text-text-main">
{t("maxLines")}
<input
type="number"
min={0}
value={config.maxLinesPerResult}
onChange={(event) =>
saveConfig({ maxLinesPerResult: Number(event.target.value) || 0 })
}
className="rounded border border-border bg-bg px-2 py-1 text-sm"
/>
</label>
<label className="flex flex-col gap-1 text-sm text-text-main">
{t("maxChars")}
<input
type="number"
min={0}
value={config.maxCharsPerResult}
onChange={(event) =>
saveConfig({ maxCharsPerResult: Number(event.target.value) || 0 })
}
className="rounded border border-border bg-bg px-2 py-1 text-sm"
/>
</label>
<label className="flex flex-col gap-1 text-sm text-text-main">
{t("deduplicateThreshold")}
<input
type="number"
min={2}
max={100}
value={config.deduplicateThreshold}
onChange={(event) =>
saveConfig({ deduplicateThreshold: Number(event.target.value) || 2 })
}
className="rounded border border-border bg-bg px-2 py-1 text-sm"
/>
</label>
<label className="flex flex-col gap-1 text-sm text-text-main">
{t("rawOutputMaxBytes")}
<input
type="number"
min={1024}
value={config.rawOutputMaxBytes}
onChange={(event) =>
saveConfig({ rawOutputMaxBytes: Number(event.target.value) || 1024 })
}
className="rounded border border-border bg-bg px-2 py-1 text-sm"
/>
</label>
</div>
<div className="mt-4 flex flex-wrap gap-4 text-sm text-text-main">
{[
["applyToToolResults", t("toolResults")],
["applyToAssistantMessages", t("assistantMessages")],
["applyToCodeBlocks", t("codeBlocks")],
["customFiltersEnabled", t("customFilters")],
["trustProjectFilters", t("trustProjectFilters")],
].map(([key, label]) => (
<label key={key} className="flex items-center gap-2">
<input
type="checkbox"
checked={Boolean(config[key as keyof RtkConfig])}
disabled={saving}
onChange={(event) =>
saveConfig({ [key]: event.target.checked } as Partial<RtkConfig>)
}
/>
{label}
</label>
))}
</div>
<div className="mt-4 max-w-sm text-sm text-text-main">
<label className="flex flex-col gap-1">
{t("rawOutputRetention")}
<select
value={config.rawOutputRetention}
disabled={saving}
onChange={(event) =>
saveConfig({
rawOutputRetention: event.target.value as RtkConfig["rawOutputRetention"],
})
}
className="rounded border border-border bg-bg px-2 py-1 text-sm"
>
<option value="never">{t("rawOutputNever")}</option>
<option value="failures">{t("rawOutputFailures")}</option>
<option value="always">{t("rawOutputAlways")}</option>
</select>
</label>
</div>
</section>
)}
{viewMode === "advanced" && (
<section className="grid grid-cols-1 gap-4 xl:grid-cols-[1fr_1fr]">
<div className="rounded-lg border border-border bg-surface p-4">
<div className="mb-3 flex items-center justify-between">
<h2 className="text-sm font-semibold text-text-main">{t("filterTesting")}</h2>
<button
onClick={runPreview}
className="rounded-lg bg-primary px-3 py-2 text-sm font-medium text-white"
>
{t("run")}
</button>
</div>
<textarea
value={sample}
onChange={(event) => setSample(event.target.value)}
placeholder={t("pasteOutput")}
className="h-72 w-full rounded-lg border border-border bg-bg p-3 font-mono text-xs text-text-main"
/>
</div>
<div className="rounded-lg border border-border bg-surface p-4">
<h2 className="mb-3 text-sm font-semibold text-text-main">{t("result")}</h2>
{preview?.detection && (
<p className="mb-2 text-xs text-text-muted">
{t("detected")}: {preview.detection.type} (
{Math.round(preview.detection.confidence * 100)}%)
</p>
)}
<pre className="h-72 overflow-auto rounded-lg border border-border bg-bg p-3 text-xs text-text-main">
{preview ? JSON.stringify(preview, null, 2) : t("previewEmpty")}
</pre>
</div>
</section>
)}
<Collapsible
title={t("filterCatalog") || "Filter Catalog"}
subtitle={t("filterCatalogDesc") || "Available output filters by category"}
icon="filter_list"
trailing={
<span className="text-xs text-text-muted">
{Object.values(groupedFilters).flat().length} {t("filtersActive") || "filters"}
</span>
}
defaultOpen={viewMode === "advanced"}
>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
{Object.entries(groupedFilters).map(([category, items]) => (
<div key={category} className="rounded-lg border border-border bg-bg p-3">
<h3 className="text-xs font-semibold capitalize text-text-main">{category}</h3>
<div className="mt-2 space-y-2">
{items.map((filter) => {
const enabled = config ? !config.disabledFilters.includes(filter.id) : true;
return (
<div
key={filter.id}
className="border-t border-border pt-2 first:border-t-0 first:pt-0"
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<p className="text-xs font-medium text-text-main">{filter.name}</p>
<p className="mt-0.5 text-[11px] text-text-muted">{filter.description}</p>
</div>
<input
type="checkbox"
checked={enabled}
disabled={!config || saving}
onChange={(event) => toggleFilter(filter.id, event.target.checked)}
className="mt-0.5"
/>
</div>
</div>
);
})}
</div>
</div>
))}
</div>
</Collapsible>
<RtkLearnDiscoverCard />
</div>
);
}