doc: README add kb

This commit is contained in:
qixinbo
2026-03-29 21:04:56 +08:00
parent 5869c377a3
commit ae6340d429
10 changed files with 120 additions and 59 deletions
+14 -44
View File
@@ -328,7 +328,20 @@ export function ChatInterface() {
// Model selection state
const [models, setModels] = useState<ModelConfig[]>([]);
const [selectedModelId, setSelectedModelId] = useState<string>("");
const [modelOpen, setModelOpen] = useState(false);
// Listen for model changes from the ProjectSwitcher
useEffect(() => {
const handleModelChange = (e: Event) => {
const customEvent = e as CustomEvent<string>;
if (customEvent.detail) {
setSelectedModelId(customEvent.detail);
}
};
window.addEventListener('nanobot:model-changed', handleModelChange);
return () => {
window.removeEventListener('nanobot:model-changed', handleModelChange);
};
}, []);
// Data Source selection state
const [availableDataSources, setAvailableDataSources] = useState<{id: string, name: string}[]>([]);
@@ -1120,49 +1133,6 @@ export function ChatInterface() {
return (
<div className="flex flex-col h-full bg-background relative">
{/* Header with Model Selection */}
<div className="px-4 py-3 flex items-center justify-between border-b border-border bg-background/50 backdrop-blur-md sticky top-0 z-20">
<Popover open={modelOpen} onOpenChange={setModelOpen}>
<PopoverTrigger className="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-muted transition-colors group">
<span className="font-semibold text-foreground">
{selectedModelId ? models.find(m => m.id === selectedModelId)?.name || 'DataClaw' : 'DataClaw'}
</span>
<ChevronDown className="h-4 w-4 text-muted-foreground group-hover:text-muted-foreground transition-colors" />
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0" align="start">
<Command>
<CommandInput placeholder={t('searchModel')} />
<CommandList className="max-h-[300px]">
<CommandEmpty>{t('modelNotFound')}</CommandEmpty>
<CommandGroup heading={t('availableModels')}>
{models.map((model) => (
<CommandItem
key={model.id}
onSelect={() => {
setSelectedModelId(model.id);
setModelOpen(false);
}}
className="flex items-center gap-2 py-2.5 cursor-pointer"
>
<div className="flex flex-col">
<span className="font-medium text-foreground">{model.name || model.model}</span>
<span className="text-xs text-muted-foreground">{model.provider}</span>
</div>
<Check
className={cn(
"ml-auto h-4 w-4",
selectedModelId === model.id ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<ScrollArea className="flex-1 min-h-0">
{/* Hidden file input available in all states */}
<input
+82 -1
View File
@@ -1,6 +1,9 @@
import { useEffect, useState } from 'react';
import { ChevronDown, Plus, Folder } from 'lucide-react';
import { ChevronDown, Plus, Folder, Check } from 'lucide-react';
import { useProjectStore } from '@/store/projectStore';
import { useTranslation } from "react-i18next";
import { api } from "@/lib/api";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
@@ -14,17 +17,51 @@ import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
interface ModelConfig {
id: string;
name: string;
model: string;
provider: string;
is_active: boolean;
}
export function ProjectSwitcher() {
const { t } = useTranslation();
const { projects, currentProject, fetchProjects, setCurrentProject, addProject } = useProjectStore();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
// Model Selection State
const [models, setModels] = useState<ModelConfig[]>([]);
const [selectedModelId, setSelectedModelId] = useState<string>("");
const [modelOpen, setModelOpen] = useState(false);
useEffect(() => {
fetchProjects();
}, [fetchProjects]);
useEffect(() => {
const fetchModels = async () => {
try {
const data = await api.get<ModelConfig[]>("/api/v1/llm");
setModels(data);
const active = data.find(m => m.is_active);
if (active) {
setSelectedModelId(active.id);
} else if (data.length > 0) {
setSelectedModelId(data[0].id);
}
} catch (e) {
console.error("Failed to fetch models", e);
}
};
fetchModels();
}, []);
const handleCreateProject = async () => {
if (!newProjectName.trim()) return;
setIsSubmitting(true);
@@ -88,6 +125,50 @@ export function ProjectSwitcher() {
</DropdownMenuContent>
</DropdownMenu>
<div className="h-4 w-px bg-border mx-1" />
<Popover open={modelOpen} onOpenChange={setModelOpen}>
<PopoverTrigger className="flex items-center gap-1 px-2 py-1 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors group">
<span className="font-semibold text-[14px]">
{selectedModelId ? (models.find(m => m.id === selectedModelId)?.name || models.find(m => m.id === selectedModelId)?.model || 'DataClaw') : 'DataClaw'}
</span>
<ChevronDown className="h-4 w-4 opacity-50 group-hover:opacity-100 transition-colors" />
</PopoverTrigger>
<PopoverContent className="w-[280px] p-0" align="start">
<Command>
<CommandInput placeholder={t('searchModel')} />
<CommandList className="max-h-[300px]">
<CommandEmpty>{t('modelNotFound')}</CommandEmpty>
<CommandGroup heading={t('availableModels')}>
{models.map((model) => (
<CommandItem
key={model.id}
onSelect={() => {
setSelectedModelId(model.id);
setModelOpen(false);
// Fire custom event to notify ChatInterface if needed
window.dispatchEvent(new CustomEvent("nanobot:model-changed", { detail: model.id }));
}}
className="flex items-center gap-2 py-2.5 cursor-pointer"
>
<div className="flex flex-col">
<span className="font-medium text-foreground">{model.name || model.model}</span>
<span className="text-xs text-muted-foreground">{model.provider}</span>
</div>
<Check
className={cn(
"ml-auto h-4 w-4",
selectedModelId === model.id ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent>
<DialogHeader>