Skip to content

Commit d0c2fb0

Browse files
committed
More embedding models, model list pulling, and more fixes
1 parent 50bc82c commit d0c2fb0

17 files changed

+291
-49
lines changed

client/src/App.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,58 @@ import { Switch, Route } from "wouter";
22
import Dashboard from "@/pages/Dashboard";
33
import { useEffect } from "react";
44
import { useStore } from "@/lib/store";
5+
import { defaultLocalModels, modelService } from "./lib/localmodels";
6+
import { PRESET_ENDPOINTS } from "./lib/constants";
7+
import { CustomModel } from "./lib/types";
58

69
function App() {
7-
const { settings } = useStore();
10+
const { settings, setSettings } = useStore();
11+
12+
useEffect(() => {
13+
const fetchModels = async () => {
14+
console.log('fetching models');
15+
// Get all endpoints from settings
16+
const endpoints = PRESET_ENDPOINTS.map(e => ({
17+
url: e.url,
18+
provider: 'openai'
19+
}));
20+
21+
// Fetch models from all configured endpoints
22+
const modelPromises = endpoints.map(e =>
23+
modelService.getAvailableModels(e.url)
24+
);
25+
26+
try {
27+
const modelLists = await Promise.allSettled(modelPromises);
28+
const allModels = modelLists
29+
.filter((result): result is PromiseFulfilledResult<CustomModel[]> =>
30+
result.status === 'fulfilled'
31+
)
32+
.flatMap(result => result.value);
33+
34+
// Merge with defaults, removing duplicates
35+
const existingIds = new Set(defaultLocalModels.map(m => m.id));
36+
const newModels = allModels.filter(m => !existingIds.has(m.id));
37+
38+
setSettings({
39+
...settings,
40+
customModels: [
41+
...settings.customModels.filter(m =>
42+
!endpoints.some(e => m.endpoint === e.url)
43+
),
44+
...defaultLocalModels,
45+
...newModels
46+
]
47+
});
48+
} catch (error) {
49+
// console.error('Error fetching models:', error);
50+
}
51+
};
52+
53+
fetchModels();
54+
const interval = setInterval(fetchModels, 120000); // 2 minutes
55+
return () => clearInterval(interval);
56+
}, []);
857

958
useEffect(() => {
1059
document.documentElement.style.setProperty('--primary-color', settings.primaryColor);

client/src/assets/vllm.svg

Lines changed: 1 addition & 0 deletions
Loading

client/src/components/AddModelDialog.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ import { PRESET_ENDPOINTS } from "@/lib/constants";
8585
/>
8686
<div className="flex flex-col">
8787
<span>{PRESET_ENDPOINTS.find(p => p.url === newModel.endpoint)?.name}</span>
88-
<span className="text-xs text-muted-foreground">
89-
{PRESET_ENDPOINTS.find(p => p.url === newModel.endpoint)?.description}
90-
</span>
9188
</div>
9289
</div>
9390
)}
@@ -96,17 +93,17 @@ import { PRESET_ENDPOINTS } from "@/lib/constants";
9693
<SelectContent>
9794
{PRESET_ENDPOINTS.map((preset) => (
9895
<SelectItem key={preset.url} value={preset.url || "custom"}>
99-
<div className="flex items-center gap-2">
96+
<div className="flex items-start gap-2">
10097
<img
10198
src={preset.icon}
10299
alt={preset.name}
103-
className="w-6 h-6 object-contain"
100+
className="w-5 h-5 object-contain"
104101
/>
105102
<div className="flex flex-col">
106103
<span>{preset.name}</span>
107-
<span className="text-xs text-muted-foreground">
104+
{/* <span className="text-xs text-muted-foreground">
108105
{preset.description}
109-
</span>
106+
</span> */}
110107
</div>
111108
</div>
112109
</SelectItem>

client/src/components/ChatNode.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import logo from "@/assets/logo.svg";
3232
import { RAGService } from "@/lib/rag";
3333
import { FileUpload } from "./FileUpload";
3434
import { encode } from "gpt-tokenizer";
35+
import { defaultLocalModels } from "@/lib/localmodels";
36+
import { modelService } from "@/lib/localmodels";
3537

3638
export function ChatNode({ id, data: initialData }: NodeProps) {
3739
const [input, setInput] = useState("");
@@ -174,6 +176,15 @@ export function ChatNode({ id, data: initialData }: NodeProps) {
174176
"";
175177
};
176178

179+
const formatSource = (metadata: any) => {
180+
console.log("Formatting source:", metadata);
181+
console.log("Type:", metadata.type);
182+
if (metadata.type === 'website') {
183+
return metadata.source;
184+
}
185+
return metadata.filename;
186+
};
187+
177188
const sendMessage = useCallback(async () => {
178189
if (!input.trim()) return;
179190

@@ -232,8 +243,9 @@ export function ChatNode({ id, data: initialData }: NodeProps) {
232243
const metadata = typeof doc.metadata === 'string'
233244
? JSON.parse(doc.metadata)
234245
: doc.metadata;
235-
console.log("Processing doc metadata:", metadata);
236-
return `[From ${metadata.filename}]: ${doc.content}`;
246+
console.log("Processing metadata:", metadata);
247+
const source = formatSource(metadata);
248+
return `[From ${source}]: ${doc.content}`;
237249
})
238250
.join('\n\n');
239251
}

client/src/components/ModelSelector.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
77
import { Info } from "lucide-react";
88
import logo from "@/assets/logo.svg"
99
import { PRESET_ENDPOINTS } from "@/lib/constants";
10+
import { modelService } from "@/lib/localmodels";
11+
import { defaultLocalModels } from "@/lib/localmodels";
12+
import { useEffect } from "react";
13+
1014
interface ModelSelectorProps {
1115
models: AIModel[];
1216
selectedModel: string;
@@ -15,6 +19,7 @@ interface ModelSelectorProps {
1519

1620
export function ModelSelector({ models, selectedModel, onSelect }: ModelSelectorProps) {
1721
const settings = useStore((state) => state.settings);
22+
const setSettings = useStore((state) => state.setSettings);
1823
const allModels = [...models, ...(settings.customModels || [])];
1924

2025
const getEndpointIcon = (model: AIModel | CustomModel) => {

client/src/components/RAGManager.tsx

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { RAGService } from "@/lib/rag";
1010
import { Trash2, Upload } from "lucide-react";
1111
import { embeddingService } from "@/lib/embeddings";
1212
import { cn } from "@/lib/utils";
13+
import { toast } from "@/hooks/use-toast";
1314

1415
interface EmbeddingProgress {
1516
status: 'idle' | 'loading' | 'chunking' | 'embedding' | 'complete' | 'error';
@@ -19,9 +20,27 @@ interface EmbeddingProgress {
1920
}
2021

2122
const EMBEDDING_MODELS = [
22-
{ id: 'Xenova/bge-base-en-v1.5', name: 'BGE Base', size: '110MB' },
23-
{ id: 'Xenova/bge-small-en-v1.5', name: 'BGE Small', size: '50MB' },
24-
{ id: 'Xenova/bge-large-en-v1.5', name: 'BGE Large', size: '330MB' }
23+
// HuggingFace embedding models
24+
{ id: 'Xenova/bge-base-en-v1.5', name: 'BGE Base English', size: '436MB', type: 'local', device: 'auto', dtype: 'fp32' },
25+
{ id: 'Xenova/bge-small-en-v1.5', name: 'BGE Small English', size: '133MB', type: 'local', device: 'auto', dtype: 'fp32' },
26+
{ id: 'Xenova/bge-large-en-v1.5', name: 'BGE Large English', size: '1.34GB', type: 'local', device: 'auto', dtype: 'fp32' },
27+
{ id: 'Xenova/nomic-embed-text-v1', name: 'Nomic Embed Text v1', size: '550MB', type: 'local', device: 'auto', dtype: 'fp32' },
28+
{ id: 'Xenova/gte-small', name: 'GTE Small', size: '133MB', type: 'local', device: 'auto', dtype: 'fp32' },
29+
{ id: 'Xenova/gte-large', name: 'GTE Large', size: '1.34GB', type: 'local', device: 'auto', dtype: 'fp32' },
30+
{ id: 'Xenova/GIST-small-Embedding-v0', name: 'GIST Small', size: '133MB', type: 'local', device: 'auto', dtype: 'fp32' },
31+
{ id: 'Xenova/ernie-3.0-nano-zh', name: 'ERNIE 3.0 Nano Chinese', size: '72MB', type: 'local', device: 'auto', dtype: 'fp32' },
32+
{ id: 'Xenova/ernie-3.0-micro-zh', name: 'ERNIE 3.0 Micro Chinese', size: '94MB', type: 'local', device: 'auto', dtype: 'fp32' },
33+
{ id: 'Xenova/ernie-3.0-mini-zh', name: 'ERNIE 3.0 Mini Chinese', size: '107MB', type: 'local', device: 'auto', dtype: 'fp32' },
34+
{ id: 'Xenova/all-roberta-large-v1', name: 'Roberta Large', size: '1.42GB', type: 'local', device: 'auto', dtype: 'fp32' },
35+
{ id: 'Xenova/jina-embeddings-v2-base-en', name: 'Jina Embeddings v2 Base English', size: '547MB', type: 'local', device: 'auto', dtype: 'fp32' },
36+
{ id: 'Xenova/jina-embeddings-v2-small-en', name: 'Jina Embeddings v2 Small English', size: '130MB', type: 'local', device: 'auto', dtype: 'fp32' },
37+
{ id: 'Xenova/jina-embeddings-v2-base-zh', name: 'Jina Embeddings v2 Base Chinese', size: '641MB', type: 'local', device: 'auto', dtype: 'fp32' },
38+
39+
// { id: 'jinaai/jina-embeddings-v3', name: 'Jina Embeddings v3', size: '2.29GB', type: 'local', device: 'auto', dtype: 'fp16' },
40+
41+
// OpenAI models (requires API key)
42+
{ id: 'text-embedding-3-small', name: 'OpenAI Embedding 3 Small', size: '', type: 'api' },
43+
{ id: 'text-embedding-3-large', name: 'OpenAI Embedding 3 Large', size: '', type: 'api' },
2544
];
2645

2746
export function RAGManager() {
@@ -109,9 +128,20 @@ export function RAGManager() {
109128
}
110129
}
111130
};
131+
const isApiModel = (modelId: string) => modelId.startsWith('text-embedding-3');
112132

113133
const handleModelChange = async (modelId: string) => {
114134
try {
135+
// Check for API key if needed
136+
if (isApiModel(modelId) && !settings.openai.apiKey) {
137+
toast({
138+
title: "API Key Required",
139+
description: "Please configure your OpenAI API key in settings first.",
140+
variant: "destructive"
141+
});
142+
return;
143+
}
144+
115145
// Update UI immediately
116146
useStore.setState({
117147
settings: {
@@ -252,6 +282,23 @@ export function RAGManager() {
252282
}
253283
};
254284

285+
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
286+
let url = e.target.value;
287+
288+
// If user is typing and hasn't included a protocol, don't modify input
289+
if (!url.includes('://')) {
290+
setUrlInput(url);
291+
return;
292+
}
293+
294+
// When pasting or finishing input, ensure https:// is present
295+
if (!url.match(/^https?:\/\//)) {
296+
url = 'https://' + url;
297+
}
298+
299+
setUrlInput(url);
300+
};
301+
255302
return (
256303
<div className="space-y-6">
257304
<p className="text-sm text-muted-foreground">
@@ -320,7 +367,7 @@ export function RAGManager() {
320367
<SelectContent>
321368
{EMBEDDING_MODELS.map(model => (
322369
<SelectItem key={model.id} value={model.id}>
323-
{model.name} ({model.size})
370+
{model.name} {model.type === 'local' && `(${model.size})`} {model.type === 'api' && '(Requires API Key)'}
324371
</SelectItem>
325372
))}
326373
</SelectContent>
@@ -348,13 +395,18 @@ export function RAGManager() {
348395
<label className="text-sm font-medium">Documents & Websites</label>
349396
<div className="mt-2">
350397
<div className="flex gap-2">
351-
<Input
352-
type="url"
353-
placeholder="Enter website URL"
354-
value={urlInput}
355-
onChange={(e) => setUrlInput(e.target.value)}
356-
disabled={isUploading}
357-
/>
398+
<Input
399+
type="url"
400+
placeholder="Enter website URL"
401+
value={urlInput}
402+
onChange={handleUrlChange}
403+
onBlur={() => {
404+
if (urlInput && !urlInput.match(/^https?:\/\//)) {
405+
setUrlInput('https://' + urlInput);
406+
}
407+
}}
408+
disabled={isUploading}
409+
/>
358410
<Button
359411
onClick={handleAddWebsite}
360412
disabled={isUploading || !urlInput}
@@ -363,8 +415,6 @@ export function RAGManager() {
363415
</Button>
364416
</div>
365417

366-
367-
368418
<Input
369419
type="file"
370420
ref={inputRef}
@@ -379,18 +429,14 @@ export function RAGManager() {
379429
{uploadingDocs.map(docId => (
380430
<div key={docId} className="flex items-center justify-between p-2 border rounded">
381431
<div className="flex-1">
382-
<p className="text-sm">Uploading...</p>
432+
<p className="text-sm">Embedding...</p>
383433
{progress[docId] && (
384434
<div className="mt-2">
385435
<Progress
386436
value={
387-
progress[docId].status === 'embedding'
388-
? (progress[docId].currentChunk / progress[docId].totalChunks) * 100
389-
: progress[docId].status === 'loading'
390-
? 20
391-
: progress[docId].status === 'chunking'
392-
? 40
393-
: 0
437+
438+
(progress[docId].currentChunk / progress[docId].totalChunks) * 100
439+
394440
}
395441
/>
396442
<p className="text-xs text-muted-foreground mt-1">

client/src/components/SettingsDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
7878

7979
<div className="flex justify-center">
8080
<img src={logo} alt="Curiso.ai" title="Curiso.ai" className="w-12 h-12" /></div>
81-
<div className="flex justify-center"><p className="text-sm text-muted-foreground justify-center mb-2">Version v1.0.9 by Carsen Klock</p></div>
81+
<div className="flex justify-center"><p className="text-sm text-muted-foreground justify-center mb-2">Version v1.1.0 by Carsen Klock</p></div>
8282
<strong>Curiso.ai</strong> is an infinite canvas for your thoughts—a platform that seamlessly connects nodes and AI services so you can explore ideas in depth without repeating yourself. By guiding the direction of each conversation, Curiso.ai empowers advanced users to unlock richer, more accurate AI interactions.
8383
</div>
8484
<div className="space-y-2 mt-2">

client/src/lib/constants.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import ollamaLogo from "@/assets/ollama-logo.svg";
22
import exoLogo from "@/assets/exo.png";
33
import janLogo from "@/assets/jan.svg";
44
import lmstudioLogo from "@/assets/lmstudio.png";
5+
import vllmlogo from "@/assets/vllm.svg";
6+
import logo from "@/assets/logo.svg";
57

68
export const themeColors = [
79
// Blues & Teals
@@ -64,10 +66,16 @@ export const DEFAULT_AI_SETTINGS = {
6466
description: "Local LM Studio instance",
6567
icon: lmstudioLogo
6668
},
69+
{
70+
name: "vLLM",
71+
url: "http://localhost:8000/v1",
72+
description: "Local vLLM instance",
73+
icon: vllmlogo
74+
},
6775
{
6876
name: "Custom",
6977
url: "",
7078
description: "Custom endpoint URL",
71-
icon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M12 8v8'%3E%3C/path%3E%3Cpath d='M8 12h8'%3E%3C/path%3E%3C/svg%3E"
79+
icon: logo
7280
}
7381
] as const;

0 commit comments

Comments
 (0)