refactor: remove unused components, mock data, and redundant logic across chat features; streamline settings and runtime handling for better maintainability

This commit is contained in:
shine1i 2026-02-02 14:52:58 +01:00
commit a87f14eccd
6 changed files with 30 additions and 245 deletions

View file

@ -18,7 +18,6 @@ import {
memo,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
@ -38,7 +37,6 @@ import {
import { ThreadSidebar } from "./thread-sidebar";
import type { ChatView } from "./types";
// TODO: fetch from API at runtime
const LORA_MODELS: ModelOption[] = [
{
id: "outputs/llama-3.1-8b-instruct-lora",
@ -75,17 +73,9 @@ const GGUF_MODELS: ModelOption[] = [
},
];
type SingleContentProps = {
threadId?: string;
};
type CompareContentProps = {
pairId: string;
};
const SingleContent = memo(function SingleContent({
threadId,
}: SingleContentProps): ReactElement {
}: { threadId?: string }): ReactElement {
return (
<ChatRuntimeProvider modelType="base" initialThreadId={threadId}>
<div className="min-h-0 flex-1">
@ -97,7 +87,7 @@ const SingleContent = memo(function SingleContent({
const CompareContent = memo(function CompareContent({
pairId,
}: CompareContentProps): ReactElement {
}: { pairId: string }): ReactElement {
const handlesRef = useRef<Record<string, CompareHandle>>({});
const [baseThreadId, setBaseThreadId] = useState<string>();
const [loraThreadId, setLoraThreadId] = useState<string>();
@ -123,9 +113,9 @@ const CompareContent = memo(function CompareContent({
return (
<CompareHandlesProvider handlesRef={handlesRef}>
<div className="flex min-h-0 flex-1 flex-col">
<div className="grid min-h-0 flex-1 grid-cols-2 px-0">
<div className="grid min-h-0 flex-1 grid-cols-2 px-0">
<div className="flex min-h-0 flex-col">
<div className=" px-3 py-1.5">
<div className="px-3 py-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
Base Model
</span>
@ -142,8 +132,8 @@ const CompareContent = memo(function CompareContent({
</div>
</div>
<div className="flex min-h-0 flex-col">
<div className=" text-end px-3 py-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-primary">
<div className="text-end px-3 py-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-primary">
Fine-tuned (LoRA)
</span>
</div>
@ -215,13 +205,10 @@ export function ChatPage(): ReactElement {
[],
);
const models = useMemo(
() =>
inferenceParams.inferenceEngine === "llama-cpp"
? GGUF_MODELS
: LORA_MODELS,
[inferenceParams.inferenceEngine],
);
const models =
inferenceParams.inferenceEngine === "llama-cpp"
? GGUF_MODELS
: LORA_MODELS;
return (
<SidebarProvider
@ -240,9 +227,7 @@ export function ChatPage(): ReactElement {
/>
</InlineSidebar>
{/* main chat area */}
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
{/* top bar */}
<div className="flex h-11 shrink-0 items-center px-2">
<div className="flex items-center gap-1.5">
<SidebarTrigger />
@ -275,7 +260,6 @@ export function ChatPage(): ReactElement {
)}
</div>
{/* inline settings panel on right */}
<ChatSettingsPanel
open={settingsOpen}
params={inferenceParams}

View file

@ -1,5 +1,3 @@
/* eslint-disable react-refresh/only-export-components */
import {
Select,
SelectContent,
@ -23,8 +21,6 @@ import { AnimatePresence, motion } from "motion/react";
import type { ReactNode } from "react";
import { useState } from "react";
// --- Types & defaults ---
export interface InferenceParams {
temperature: number;
topP: number;
@ -33,7 +29,7 @@ export interface InferenceParams {
maxTokens: number;
systemPrompt: string;
inferenceEngine: string;
checkpoint: string; // "" = no checkpoint (base model only)
checkpoint: string;
}
export const defaultInferenceParams: InferenceParams = {
@ -81,8 +77,6 @@ const ENGINE_OPTIONS = [
{ value: "llama-cpp", label: "llama.cpp (GGUF)" },
];
// --- Subcomponents ---
function ParamSlider({
label,
value,
@ -166,8 +160,6 @@ function CollapsibleSection({
);
}
// --- Main panel ---
interface ChatSettingsPanelProps {
open: boolean;
params: InferenceParams;
@ -182,8 +174,9 @@ export function ChatSettingsPanel({
const [presets, setPresets] = useState<Preset[]>(BUILTIN_PRESETS);
const [activePreset, setActivePreset] = useState("Default");
const set = (key: keyof InferenceParams) => (v: number | string) =>
onParamsChange({ ...params, [key]: v });
function set<K extends keyof InferenceParams>(key: K) {
return (v: InferenceParams[K]) => onParamsChange({ ...params, [key]: v });
}
function applyPreset(name: string) {
const p = presets.find((pr) => pr.name === name);
@ -218,10 +211,9 @@ export function ChatSettingsPanel({
return (
<aside
className={`shrink-0 h-full overflow-hidden bg-sidebar transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-sidebar-border" : "w-0"}`}
className={`shrink-0 h-full overflow-hidden bg-sidebar transition-[width] duration-200 ease-linear ${open ? "w-[17rem] border-sidebar-border" : "w-0"}`}
>
<div className="flex h-full w-[17rem] flex-col">
{/* header */}
<div className="flex items-center gap-2 px-3 py-2">
<HugeiconsIcon
icon={PencilEdit01Icon}
@ -233,7 +225,6 @@ export function ChatSettingsPanel({
</div>
<div className="flex-1 overflow-y-auto px-1.5">
{/* presets */}
<div className="px-2 pb-3">
<div className="flex items-center gap-2">
<Select value={activePreset} onValueChange={applyPreset}>
@ -279,7 +270,6 @@ export function ChatSettingsPanel({
</div>
</div>
{/* system prompt — always visible */}
<div className="px-2 pb-4">
<label
htmlFor="system-prompt"
@ -297,7 +287,6 @@ export function ChatSettingsPanel({
/>
</div>
{/* inference engine */}
<CollapsibleSection
icon={EngineIcon}
label="Inference Engine"
@ -309,7 +298,7 @@ export function ChatSettingsPanel({
</span>
<Select
value={params.inferenceEngine}
onValueChange={(v) => set("inferenceEngine")(v)}
onValueChange={set("inferenceEngine")}
>
<SelectTrigger className="h-8 w-full text-xs corner-squircle">
<SelectValue />
@ -325,20 +314,19 @@ export function ChatSettingsPanel({
</div>
</CollapsibleSection>
{/* sampling */}
<CollapsibleSection
icon={SlidersHorizontalIcon}
label="Sampling"
defaultOpen={true}
>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-5">
<ParamSlider
label="Temperature"
value={params.temperature}
min={0}
max={2}
step={0.1}
onChange={set("temperature") as (v: number) => void}
onChange={set("temperature")}
/>
<ParamSlider
label="Top P"
@ -346,7 +334,7 @@ export function ChatSettingsPanel({
min={0}
max={1}
step={0.05}
onChange={set("topP") as (v: number) => void}
onChange={set("topP")}
/>
<ParamSlider
label="Top K"
@ -354,7 +342,7 @@ export function ChatSettingsPanel({
min={0}
max={100}
step={1}
onChange={set("topK") as (v: number) => void}
onChange={set("topK")}
/>
<ParamSlider
label="Repetition Penalty"
@ -362,7 +350,7 @@ export function ChatSettingsPanel({
min={1}
max={2}
step={0.05}
onChange={set("repetitionPenalty") as (v: number) => void}
onChange={set("repetitionPenalty")}
/>
<ParamSlider
label="Max Tokens"
@ -370,12 +358,11 @@ export function ChatSettingsPanel({
min={64}
max={4096}
step={64}
onChange={set("maxTokens") as (v: number) => void}
onChange={set("maxTokens")}
/>
</div>
</CollapsibleSection>
{/* settings — empty for now */}
<CollapsibleSection icon={Settings02Icon} label="Settings">
<p className="text-xs text-muted-foreground">
No additional settings yet.

View file

@ -1,151 +0,0 @@
/* eslint-disable react-refresh/only-export-components */
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import {
Logout01Icon,
Settings04Icon,
SidebarLeft01Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
export interface ModelOption {
value: string;
label: string;
}
// TODO: fetch from GET /api/loras at runtime
export const MOCK_CHECKPOINTS: ModelOption[] = [
{
value: "outputs/llama-3.1-8b-instruct-lora",
label: "meta-llama/Llama-3.1-8B-Instruct — LoRA v1",
},
{
value: "outputs/qwen2.5-7b-lora",
label: "Qwen/Qwen2.5-7B-Instruct — LoRA v2",
},
{
value: "outputs/mistral-7b-v0.3-lora",
label: "mistralai/Mistral-7B-Instruct-v0.3 — LoRA v1",
},
];
export const MOCK_GGUFS: ModelOption[] = [
{
value: "models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
label: "Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
},
{
value: "models/Qwen2.5-7B-Instruct-Q5_K_M.gguf",
label: "Qwen2.5-7B-Instruct-Q5_K_M.gguf",
},
{
value: "models/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf",
label: "Mistral-7B-Instruct-v0.3-Q4_K_M.gguf",
},
];
export const DEFAULT_CHECKPOINT = MOCK_CHECKPOINTS[0].value;
interface ChatTopBarProps {
checkpoint: string;
inferenceEngine: string;
onCheckpointChange: (value: string) => void;
onEject: () => void;
sidebarOpen: boolean;
onSidebarToggle: () => void;
settingsOpen: boolean;
onSettingsToggle: () => void;
}
export function ChatTopBar({
checkpoint,
inferenceEngine,
onCheckpointChange,
onEject,
sidebarOpen,
onSidebarToggle,
settingsOpen,
onSettingsToggle,
}: ChatTopBarProps) {
const isLoaded = checkpoint !== "";
const items = inferenceEngine === "llama-cpp" ? MOCK_GGUFS : MOCK_CHECKPOINTS;
return (
<div className="flex h-11 shrink-0 items-center gap-2 px-3">
{/* sidebar toggle */}
<button
type="button"
onClick={onSidebarToggle}
className={cn(
"flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent",
sidebarOpen && "bg-accent text-foreground",
)}
title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
>
<HugeiconsIcon icon={SidebarLeft01Icon} className="size-4" />
</button>
{/* center group: settings + model selector + eject */}
<div className="flex flex-1 items-center justify-center gap-1.5">
<button
type="button"
onClick={onSettingsToggle}
className={cn(
"flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent",
settingsOpen && "bg-accent text-foreground",
)}
title="Inference settings"
>
<HugeiconsIcon icon={Settings04Icon} className="size-4" />
</button>
<Select value={checkpoint} onValueChange={onCheckpointChange}>
<SelectTrigger className="h-8 min-w-[320px] text-xs">
{isLoaded && (
<span className="size-2 shrink-0 rounded-full bg-emerald-500" />
)}
<SelectValue placeholder="Select a model…" />
</SelectTrigger>
<SelectContent position="popper" className="min-w-[340px]">
<SelectGroup>
<SelectLabel>
{inferenceEngine === "llama-cpp"
? "GGUF Models"
: "LoRA Checkpoints"}
</SelectLabel>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<button
type="button"
onClick={onEject}
disabled={!isLoaded}
className={cn(
"flex h-8 items-center gap-1.5 rounded-md border px-2.5 text-xs transition-colors",
isLoaded
? "text-muted-foreground hover:bg-accent hover:text-foreground"
: "cursor-default border-transparent text-muted-foreground/30",
)}
>
<HugeiconsIcon icon={Logout01Icon} className="size-3.5" />
Eject
</button>
</div>
</div>
);
}

View file

@ -18,16 +18,10 @@ import {
unstable_useRemoteThreadListRuntime as useRemoteThreadListRuntime,
} from "@assistant-ui/react";
import { createAssistantStream } from "assistant-stream";
import {
type MutableRefObject,
type ReactElement,
type ReactNode,
useEffect,
useMemo,
} from "react";
import { type ReactElement, type ReactNode, useEffect, useMemo } from "react";
import { createStreamAdapter } from "./adapter";
import { db } from "./db";
import type { MessageRecord, ModelType, RuntimeBridge } from "./types";
import type { MessageRecord, ModelType } from "./types";
function toThreadMessage(m: MessageRecord): ThreadMessage {
const base = {
@ -61,10 +55,6 @@ function toThreadMessage(m: MessageRecord): ThreadMessage {
};
}
function threadStatus(archived: boolean): "archived" | "regular" {
return archived ? "archived" : "regular";
}
function createDexieAdapter(
modelType: ModelType,
pairId?: string,
@ -77,7 +67,7 @@ function createDexieAdapter(
}
return {
remoteId: thread.id,
status: threadStatus(thread.archived),
status: thread.archived ? "archived" : "regular",
title: thread.title,
};
},
@ -90,7 +80,9 @@ function createDexieAdapter(
.sortBy("createdAt");
return {
threads: threads.map((t) => ({
status: threadStatus(t.archived),
status: (t.archived ? "archived" : "regular") as
| "archived"
| "regular",
remoteId: t.id,
title: t.title,
})),
@ -174,8 +166,7 @@ function ThreadHistoryProvider({
.equals(remoteId)
.sortBy("createdAt");
const converted = msgs.map((m) => toThreadMessage(m));
return ExportedMessageRepository.fromArray(converted);
return ExportedMessageRepository.fromArray(msgs.map(toThreadMessage));
},
async append({ message }: ExportedMessageRepositoryItem) {
@ -228,24 +219,6 @@ const chatAdapter = createStreamAdapter();
const useRuntimeHook = (): ReturnType<typeof useLocalRuntime> =>
useLocalRuntime(chatAdapter);
function RuntimeBridgeCapture({
bridgeRef,
}: {
bridgeRef: MutableRefObject<RuntimeBridge | null>;
}): ReactElement | null {
const runtime = useAssistantRuntime();
useEffect(() => {
bridgeRef.current = {
switchToThread: (id) => runtime.threadList.switchToThread(id),
switchToNewThread: () => runtime.threadList.switchToNewThread(),
};
return () => {
bridgeRef.current = null;
};
}, [runtime, bridgeRef]);
return null;
}
function ThreadAutoSwitch({
threadId,
}: { threadId: string }): ReactElement | null {
@ -278,13 +251,11 @@ export function ChatRuntimeProvider({
children,
modelType = "base",
pairId,
bridgeRef,
initialThreadId,
}: {
children: ReactNode;
modelType?: ModelType;
pairId?: string;
bridgeRef?: MutableRefObject<RuntimeBridge | null>;
initialThreadId?: string;
}): ReactElement {
const runtime = useRemoteThreadListRuntime({
@ -298,7 +269,7 @@ export function ChatRuntimeProvider({
const aui = useAui({
suggestions: Suggestions([
"Draw a simple flowchart of a login system using Mermaid",
"Solve the integral of x²·sin(x) step by step",
"Solve the integral of x\u00B2\u00B7sin(x) step by step",
"Write a Python function that finds the longest palindrome in a string",
"Format a comparison of 3 databases as a markdown table with pros and cons",
]),
@ -306,7 +277,6 @@ export function ChatRuntimeProvider({
return (
<AssistantRuntimeProvider runtime={runtime} aui={aui}>
{bridgeRef && <RuntimeBridgeCapture bridgeRef={bridgeRef} />}
{initialThreadId && <ThreadAutoSwitch threadId={initialThreadId} />}
{children}
</AssistantRuntimeProvider>

View file

@ -124,7 +124,6 @@ export function ThreadSidebar({
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
{/*<SidebarSeparator />*/}
<SidebarGroup className="flex-1">
<SidebarGroupLabel>Your Chats</SidebarGroupLabel>
<SidebarGroupContent>

View file

@ -22,7 +22,3 @@ export interface MessageRecord {
createdAt: number;
}
export interface RuntimeBridge {
switchToThread: (threadId: string) => void;
switchToNewThread: () => void;
}