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:
parent
0d30950b75
commit
a87f14eccd
6 changed files with 30 additions and 245 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ export function ThreadSidebar({
|
|||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
{/*<SidebarSeparator />*/}
|
||||
<SidebarGroup className="flex-1">
|
||||
<SidebarGroupLabel>Your Chats</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,3 @@ export interface MessageRecord {
|
|||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface RuntimeBridge {
|
||||
switchToThread: (threadId: string) => void;
|
||||
switchToNewThread: () => void;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue