mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-04-30 16:22:29 +00:00
Compare commits
3 Commits
master
...
8e133366b2
Author | SHA1 | Date | |
---|---|---|---|
|
8e133366b2 | ||
|
0ebda34a52 | ||
|
1deda793bd |
16
README.md
16
README.md
@ -1,5 +1,21 @@
|
|||||||
# 🚀 Perplexica - An AI-powered search engine 🔎 <!-- omit in toc -->
|
# 🚀 Perplexica - An AI-powered search engine 🔎 <!-- omit in toc -->
|
||||||
|
|
||||||
|
<div align="center" markdown="1">
|
||||||
|
<sup>Special thanks to:</sup>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<a href="https://www.warp.dev/perplexica">
|
||||||
|
<img alt="Warp sponsorship" width="400" src="https://github.com/user-attachments/assets/775dd593-9b5f-40f1-bf48-479faff4c27b">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### [Warp, the AI Devtool that lives in your terminal](https://www.warp.dev/perplexica)
|
||||||
|
|
||||||
|
[Available for MacOS, Linux, & Windows](https://www.warp.dev/perplexica)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
[](https://discord.gg/26aArMy8tT)
|
[](https://discord.gg/26aArMy8tT)
|
||||||
|
|
||||||

|

|
||||||
|
@ -25,8 +25,5 @@ API_URL = "" # Ollama API URL - http://host.docker.internal:11434
|
|||||||
[MODELS.DEEPSEEK]
|
[MODELS.DEEPSEEK]
|
||||||
API_KEY = ""
|
API_KEY = ""
|
||||||
|
|
||||||
[MODELS.LM_STUDIO]
|
|
||||||
API_URL = "" # LM Studio API URL - http://host.docker.internal:1234
|
|
||||||
|
|
||||||
[API_ENDPOINTS]
|
[API_ENDPOINTS]
|
||||||
SEARXNG = "" # SearxNG API URL - http://localhost:32768
|
SEARXNG = "" # SearxNG API URL - http://localhost:32768
|
@ -8,7 +8,6 @@ import {
|
|||||||
getOllamaApiEndpoint,
|
getOllamaApiEndpoint,
|
||||||
getOpenaiApiKey,
|
getOpenaiApiKey,
|
||||||
getDeepseekApiKey,
|
getDeepseekApiKey,
|
||||||
getLMStudioApiEndpoint,
|
|
||||||
updateConfig,
|
updateConfig,
|
||||||
} from '@/lib/config';
|
} from '@/lib/config';
|
||||||
import {
|
import {
|
||||||
@ -52,7 +51,6 @@ export const GET = async (req: Request) => {
|
|||||||
|
|
||||||
config['openaiApiKey'] = getOpenaiApiKey();
|
config['openaiApiKey'] = getOpenaiApiKey();
|
||||||
config['ollamaApiUrl'] = getOllamaApiEndpoint();
|
config['ollamaApiUrl'] = getOllamaApiEndpoint();
|
||||||
config['lmStudioApiUrl'] = getLMStudioApiEndpoint();
|
|
||||||
config['anthropicApiKey'] = getAnthropicApiKey();
|
config['anthropicApiKey'] = getAnthropicApiKey();
|
||||||
config['groqApiKey'] = getGroqApiKey();
|
config['groqApiKey'] = getGroqApiKey();
|
||||||
config['geminiApiKey'] = getGeminiApiKey();
|
config['geminiApiKey'] = getGeminiApiKey();
|
||||||
@ -95,9 +93,6 @@ export const POST = async (req: Request) => {
|
|||||||
DEEPSEEK: {
|
DEEPSEEK: {
|
||||||
API_KEY: config.deepseekApiKey,
|
API_KEY: config.deepseekApiKey,
|
||||||
},
|
},
|
||||||
LM_STUDIO: {
|
|
||||||
API_URL: config.lmStudioApiUrl,
|
|
||||||
},
|
|
||||||
CUSTOM_OPENAI: {
|
CUSTOM_OPENAI: {
|
||||||
API_URL: config.customOpenaiApiUrl,
|
API_URL: config.customOpenaiApiUrl,
|
||||||
API_KEY: config.customOpenaiApiKey,
|
API_KEY: config.customOpenaiApiKey,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import DeleteChat from '@/components/DeleteChat';
|
import DeleteChat from '@/components/DeleteChat';
|
||||||
|
import BatchDeleteChats from '@/components/BatchDeleteChats';
|
||||||
import { cn, formatTimeDifference } from '@/lib/utils';
|
import { cn, formatTimeDifference } from '@/lib/utils';
|
||||||
import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react';
|
import { BookOpenText, Check, ClockIcon, Delete, ScanEye, Search, X } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export interface Chat {
|
export interface Chat {
|
||||||
id: string;
|
id: string;
|
||||||
@ -15,7 +17,13 @@ export interface Chat {
|
|||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
const [chats, setChats] = useState<Chat[]>([]);
|
const [chats, setChats] = useState<Chat[]>([]);
|
||||||
|
const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [selectionMode, setSelectionMode] = useState(false);
|
||||||
|
const [selectedChats, setSelectedChats] = useState<string[]>([]);
|
||||||
|
const [hoveredChatId, setHoveredChatId] = useState<string | null>(null);
|
||||||
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchChats = async () => {
|
const fetchChats = async () => {
|
||||||
@ -31,12 +39,71 @@ const Page = () => {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
setChats(data.chats);
|
setChats(data.chats);
|
||||||
|
setFilteredChats(data.chats);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchChats();
|
fetchChats();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQuery.trim() === '') {
|
||||||
|
setFilteredChats(chats);
|
||||||
|
} else {
|
||||||
|
const filtered = chats.filter((chat) =>
|
||||||
|
chat.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilteredChats(filtered);
|
||||||
|
}
|
||||||
|
}, [searchQuery, chats]);
|
||||||
|
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchQuery(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
setSearchQuery('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSelectionMode = () => {
|
||||||
|
setSelectionMode(!selectionMode);
|
||||||
|
setSelectedChats([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleChatSelection = (chatId: string) => {
|
||||||
|
if (selectedChats.includes(chatId)) {
|
||||||
|
setSelectedChats(selectedChats.filter(id => id !== chatId));
|
||||||
|
} else {
|
||||||
|
setSelectedChats([...selectedChats, chatId]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAllChats = () => {
|
||||||
|
if (selectedChats.length === filteredChats.length) {
|
||||||
|
setSelectedChats([]);
|
||||||
|
} else {
|
||||||
|
setSelectedChats(filteredChats.map(chat => chat.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSelectedChats = () => {
|
||||||
|
if (selectedChats.length === 0) return;
|
||||||
|
setIsDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBatchDeleteComplete = () => {
|
||||||
|
setSelectedChats([]);
|
||||||
|
setSelectionMode(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChatsAfterDelete = (newChats: Chat[]) => {
|
||||||
|
setChats(newChats);
|
||||||
|
setFilteredChats(newChats.filter(chat =>
|
||||||
|
searchQuery.trim() === '' ||
|
||||||
|
chat.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
return loading ? (
|
return loading ? (
|
||||||
<div className="flex flex-row items-center justify-center min-h-screen">
|
<div className="flex flex-row items-center justify-center min-h-screen">
|
||||||
<svg
|
<svg
|
||||||
@ -64,32 +131,145 @@ const Page = () => {
|
|||||||
<h1 className="text-3xl font-medium p-2">Library</h1>
|
<h1 className="text-3xl font-medium p-2">Library</h1>
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-t border-[#2B2C2C] my-4 w-full" />
|
<hr className="border-t border-[#2B2C2C] my-4 w-full" />
|
||||||
|
|
||||||
|
{/* Search Box */}
|
||||||
|
<div className="relative mt-6 mb-6">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<Search className="w-5 h-5 text-black/50 dark:text-white/50" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="block w-full p-2 pl-10 pr-10 bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 rounded-md text-black dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||||
|
placeholder="Search your threads..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
onClick={clearSearch}
|
||||||
|
className="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5 text-black/50 dark:text-white/50 hover:text-black dark:hover:text-white" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thread Count and Selection Controls */}
|
||||||
|
<div className="mb-4">
|
||||||
|
{!selectionMode ? (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-black/70 dark:text-white/70">
|
||||||
|
You have {chats.length} threads in Perplexica
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={toggleSelectionMode}
|
||||||
|
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-black/70 dark:text-white/70">
|
||||||
|
{selectedChats.length} selected thread{selectedChats.length !== 1 ? 's' : ''}
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<button
|
||||||
|
onClick={selectAllChats}
|
||||||
|
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||||
|
>
|
||||||
|
{selectedChats.length === filteredChats.length ? 'Deselect all' : 'Select all'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={toggleSelectionMode}
|
||||||
|
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={deleteSelectedChats}
|
||||||
|
disabled={selectedChats.length === 0}
|
||||||
|
className={cn(
|
||||||
|
"text-sm transition duration-200",
|
||||||
|
selectedChats.length === 0
|
||||||
|
? "text-red-400/50 hover:text-red-500/50 cursor-not-allowed"
|
||||||
|
: "text-red-400 hover:text-red-500 cursor-pointer"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Delete Selected
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{chats.length === 0 && (
|
|
||||||
<div className="flex flex-row items-center justify-center min-h-screen">
|
{filteredChats.length === 0 && (
|
||||||
|
<div className="flex flex-row items-center justify-center min-h-[50vh]">
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
No chats found.
|
{searchQuery ? 'No threads found matching your search.' : 'No threads found.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chats.length > 0 && (
|
|
||||||
|
{filteredChats.length > 0 && (
|
||||||
<div className="flex flex-col pb-20 lg:pb-2">
|
<div className="flex flex-col pb-20 lg:pb-2">
|
||||||
{chats.map((chat, i) => (
|
{filteredChats.map((chat, i) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col space-y-4 py-6',
|
'flex flex-col space-y-4 py-6',
|
||||||
i !== chats.length - 1
|
i !== filteredChats.length - 1
|
||||||
? 'border-b border-white-200 dark:border-dark-200'
|
? 'border-b border-white-200 dark:border-dark-200'
|
||||||
: '',
|
: '',
|
||||||
)}
|
)}
|
||||||
key={i}
|
key={i}
|
||||||
|
onMouseEnter={() => setHoveredChatId(chat.id)}
|
||||||
|
onMouseLeave={() => setHoveredChatId(null)}
|
||||||
>
|
>
|
||||||
<Link
|
<div className="flex items-center">
|
||||||
href={`/c/${chat.id}`}
|
{/* Checkbox - visible when in selection mode or when hovering */}
|
||||||
className="text-black dark:text-white lg:text-xl font-medium truncate transition duration-200 hover:text-[#24A0ED] dark:hover:text-[#24A0ED] cursor-pointer"
|
{(selectionMode || hoveredChatId === chat.id) && (
|
||||||
>
|
<div
|
||||||
{chat.title}
|
className="mr-3 cursor-pointer"
|
||||||
</Link>
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!selectionMode) setSelectionMode(true);
|
||||||
|
toggleChatSelection(chat.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={cn(
|
||||||
|
"w-5 h-5 border rounded flex items-center justify-center transition-colors",
|
||||||
|
selectedChats.includes(chat.id)
|
||||||
|
? "bg-blue-500 border-blue-500"
|
||||||
|
: "border-gray-400 dark:border-gray-600"
|
||||||
|
)}>
|
||||||
|
{selectedChats.includes(chat.id) && (
|
||||||
|
<Check className="w-4 h-4 text-white" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Chat Title */}
|
||||||
|
<Link
|
||||||
|
href={`/c/${chat.id}`}
|
||||||
|
className={cn(
|
||||||
|
"text-black dark:text-white lg:text-xl font-medium truncate transition duration-200 hover:text-[#24A0ED] dark:hover:text-[#24A0ED] cursor-pointer",
|
||||||
|
selectionMode && "pointer-events-none text-black dark:text-white hover:text-black dark:hover:text-white"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (selectionMode) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleChatSelection(chat.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{chat.title}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row items-center justify-between w-full">
|
<div className="flex flex-row items-center justify-between w-full">
|
||||||
<div className="flex flex-row items-center space-x-1 lg:space-x-1.5 text-black/70 dark:text-white/70">
|
<div className="flex flex-row items-center space-x-1 lg:space-x-1.5 text-black/70 dark:text-white/70">
|
||||||
<ClockIcon size={15} />
|
<ClockIcon size={15} />
|
||||||
@ -97,16 +277,30 @@ const Page = () => {
|
|||||||
{formatTimeDifference(new Date(), chat.createdAt)} Ago
|
{formatTimeDifference(new Date(), chat.createdAt)} Ago
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<DeleteChat
|
|
||||||
chatId={chat.id}
|
{/* Delete button - only visible when not in selection mode */}
|
||||||
chats={chats}
|
{!selectionMode && (
|
||||||
setChats={setChats}
|
<DeleteChat
|
||||||
/>
|
chatId={chat.id}
|
||||||
|
chats={chats}
|
||||||
|
setChats={updateChatsAfterDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Batch Delete Confirmation Dialog */}
|
||||||
|
<BatchDeleteChats
|
||||||
|
chatIds={selectedChats}
|
||||||
|
chats={chats}
|
||||||
|
setChats={updateChatsAfterDelete}
|
||||||
|
onComplete={handleBatchDeleteComplete}
|
||||||
|
isOpen={isDeleteDialogOpen}
|
||||||
|
setIsOpen={setIsDeleteDialogOpen}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,6 @@ import { Switch } from '@headlessui/react';
|
|||||||
import ThemeSwitcher from '@/components/theme/Switcher';
|
import ThemeSwitcher from '@/components/theme/Switcher';
|
||||||
import { ImagesIcon, VideoIcon } from 'lucide-react';
|
import { ImagesIcon, VideoIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { PROVIDER_METADATA } from '@/lib/providers';
|
|
||||||
|
|
||||||
interface SettingsType {
|
interface SettingsType {
|
||||||
chatModelProviders: {
|
chatModelProviders: {
|
||||||
@ -21,7 +20,6 @@ interface SettingsType {
|
|||||||
anthropicApiKey: string;
|
anthropicApiKey: string;
|
||||||
geminiApiKey: string;
|
geminiApiKey: string;
|
||||||
ollamaApiUrl: string;
|
ollamaApiUrl: string;
|
||||||
lmStudioApiUrl: string;
|
|
||||||
deepseekApiKey: string;
|
deepseekApiKey: string;
|
||||||
customOpenaiApiKey: string;
|
customOpenaiApiKey: string;
|
||||||
customOpenaiApiUrl: string;
|
customOpenaiApiUrl: string;
|
||||||
@ -550,9 +548,8 @@ const Page = () => {
|
|||||||
(provider) => ({
|
(provider) => ({
|
||||||
value: provider,
|
value: provider,
|
||||||
label:
|
label:
|
||||||
(PROVIDER_METADATA as any)[provider]?.displayName ||
|
|
||||||
provider.charAt(0).toUpperCase() +
|
provider.charAt(0).toUpperCase() +
|
||||||
provider.slice(1),
|
provider.slice(1),
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -693,9 +690,8 @@ const Page = () => {
|
|||||||
(provider) => ({
|
(provider) => ({
|
||||||
value: provider,
|
value: provider,
|
||||||
label:
|
label:
|
||||||
(PROVIDER_METADATA as any)[provider]?.displayName ||
|
|
||||||
provider.charAt(0).toUpperCase() +
|
provider.charAt(0).toUpperCase() +
|
||||||
provider.slice(1),
|
provider.slice(1),
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -862,25 +858,6 @@ const Page = () => {
|
|||||||
onSave={(value) => saveConfig('deepseekApiKey', value)}
|
onSave={(value) => saveConfig('deepseekApiKey', value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
LM Studio API URL
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="LM Studio API URL"
|
|
||||||
value={config.lmStudioApiUrl}
|
|
||||||
isSaving={savingStates['lmStudioApiUrl']}
|
|
||||||
onChange={(e) => {
|
|
||||||
setConfig((prev) => ({
|
|
||||||
...prev!,
|
|
||||||
lmStudioApiUrl: e.target.value,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
onSave={(value) => saveConfig('lmStudioApiUrl', value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</div>
|
</div>
|
||||||
|
118
src/components/BatchDeleteChats.tsx
Normal file
118
src/components/BatchDeleteChats.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
Description,
|
||||||
|
Dialog,
|
||||||
|
DialogBackdrop,
|
||||||
|
DialogPanel,
|
||||||
|
DialogTitle,
|
||||||
|
Transition,
|
||||||
|
TransitionChild,
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import { Fragment, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { Chat } from '@/app/library/page';
|
||||||
|
|
||||||
|
interface BatchDeleteChatsProps {
|
||||||
|
chatIds: string[];
|
||||||
|
chats: Chat[];
|
||||||
|
setChats: (chats: Chat[]) => void;
|
||||||
|
onComplete: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BatchDeleteChats = ({
|
||||||
|
chatIds,
|
||||||
|
chats,
|
||||||
|
setChats,
|
||||||
|
onComplete,
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
}: BatchDeleteChatsProps) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (chatIds.length === 0) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
for (const chatId of chatIds) {
|
||||||
|
await fetch(`/api/chats/${chatId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChats = chats.filter(chat => !chatIds.includes(chat.id));
|
||||||
|
setChats(newChats);
|
||||||
|
|
||||||
|
toast.success(`${chatIds.length} thread${chatIds.length > 1 ? 's' : ''} deleted`);
|
||||||
|
onComplete();
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error('Failed to delete threads');
|
||||||
|
} finally {
|
||||||
|
setIsOpen(false);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
|
<Dialog
|
||||||
|
as="div"
|
||||||
|
className="relative z-50"
|
||||||
|
onClose={() => {
|
||||||
|
if (!loading) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogBackdrop className="fixed inset-0 bg-black/30" />
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
|
<TransitionChild
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100 scale-200"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<DialogPanel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<DialogTitle className="text-lg font-medium leading-6 dark:text-white">
|
||||||
|
Delete Confirmation
|
||||||
|
</DialogTitle>
|
||||||
|
<Description className="text-sm dark:text-white/70 text-black/70">
|
||||||
|
Are you sure you want to delete {chatIds.length} selected thread{chatIds.length !== 1 ? 's' : ''}?
|
||||||
|
</Description>
|
||||||
|
<div className="flex flex-row items-end justify-end space-x-4 mt-6">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (!loading) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-black/50 dark:text-white/50 text-sm hover:text-black/70 hover:dark:text-white/70 transition duration-200"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
className="text-red-400 text-sm hover:text-red-500 transition duration-200"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogPanel>
|
||||||
|
</TransitionChild>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BatchDeleteChats;
|
@ -1,14 +1,7 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import toml from '@iarna/toml';
|
import toml from '@iarna/toml';
|
||||||
|
|
||||||
// Use dynamic imports for Node.js modules to prevent client-side errors
|
|
||||||
let fs: any;
|
|
||||||
let path: any;
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
// We're on the server
|
|
||||||
fs = require('fs');
|
|
||||||
path = require('path');
|
|
||||||
}
|
|
||||||
|
|
||||||
const configFileName = 'config.toml';
|
const configFileName = 'config.toml';
|
||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
@ -35,9 +28,6 @@ interface Config {
|
|||||||
DEEPSEEK: {
|
DEEPSEEK: {
|
||||||
API_KEY: string;
|
API_KEY: string;
|
||||||
};
|
};
|
||||||
LM_STUDIO: {
|
|
||||||
API_URL: string;
|
|
||||||
};
|
|
||||||
CUSTOM_OPENAI: {
|
CUSTOM_OPENAI: {
|
||||||
API_URL: string;
|
API_URL: string;
|
||||||
API_KEY: string;
|
API_KEY: string;
|
||||||
@ -53,17 +43,10 @@ type RecursivePartial<T> = {
|
|||||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadConfig = () => {
|
const loadConfig = () =>
|
||||||
// Server-side only
|
toml.parse(
|
||||||
if (typeof window === 'undefined') {
|
fs.readFileSync(path.join(process.cwd(), `${configFileName}`), 'utf-8'),
|
||||||
return toml.parse(
|
) as any as Config;
|
||||||
fs.readFileSync(path.join(process.cwd(), `${configFileName}`), 'utf-8'),
|
|
||||||
) as any as Config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client-side fallback - settings will be loaded via API
|
|
||||||
return {} as Config;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSimilarityMeasure = () =>
|
export const getSimilarityMeasure = () =>
|
||||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||||
@ -94,9 +77,6 @@ export const getCustomOpenaiApiUrl = () =>
|
|||||||
export const getCustomOpenaiModelName = () =>
|
export const getCustomOpenaiModelName = () =>
|
||||||
loadConfig().MODELS.CUSTOM_OPENAI.MODEL_NAME;
|
loadConfig().MODELS.CUSTOM_OPENAI.MODEL_NAME;
|
||||||
|
|
||||||
export const getLMStudioApiEndpoint = () =>
|
|
||||||
loadConfig().MODELS.LM_STUDIO.API_URL;
|
|
||||||
|
|
||||||
const mergeConfigs = (current: any, update: any): any => {
|
const mergeConfigs = (current: any, update: any): any => {
|
||||||
if (update === null || update === undefined) {
|
if (update === null || update === undefined) {
|
||||||
return current;
|
return current;
|
||||||
@ -129,13 +109,10 @@ const mergeConfigs = (current: any, update: any): any => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const updateConfig = (config: RecursivePartial<Config>) => {
|
export const updateConfig = (config: RecursivePartial<Config>) => {
|
||||||
// Server-side only
|
const currentConfig = loadConfig();
|
||||||
if (typeof window === 'undefined') {
|
const mergedConfig = mergeConfigs(currentConfig, config);
|
||||||
const currentConfig = loadConfig();
|
fs.writeFileSync(
|
||||||
const mergedConfig = mergeConfigs(currentConfig, config);
|
path.join(path.join(process.cwd(), `${configFileName}`)),
|
||||||
fs.writeFileSync(
|
toml.stringify(mergedConfig),
|
||||||
path.join(path.join(process.cwd(), `${configFileName}`)),
|
);
|
||||||
toml.stringify(mergedConfig),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { ChatAnthropic } from '@langchain/anthropic';
|
import { ChatAnthropic } from '@langchain/anthropic';
|
||||||
import { ChatModel } from '.';
|
import { ChatModel } from '.';
|
||||||
import { getAnthropicApiKey } from '../config';
|
import { getAnthropicApiKey } from '../config';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'anthropic',
|
|
||||||
displayName: 'Anthropic',
|
|
||||||
};
|
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
|
|
||||||
const anthropicChatModels: Record<string, string>[] = [
|
const anthropicChatModels: Record<string, string>[] = [
|
||||||
|
@ -3,11 +3,6 @@ import { getDeepseekApiKey } from '../config';
|
|||||||
import { ChatModel } from '.';
|
import { ChatModel } from '.';
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'deepseek',
|
|
||||||
displayName: 'Deepseek AI',
|
|
||||||
};
|
|
||||||
|
|
||||||
const deepseekChatModels: Record<string, string>[] = [
|
const deepseekChatModels: Record<string, string>[] = [
|
||||||
{
|
{
|
||||||
displayName: 'Deepseek Chat (Deepseek V3)',
|
displayName: 'Deepseek Chat (Deepseek V3)',
|
||||||
|
@ -4,11 +4,6 @@ import {
|
|||||||
} from '@langchain/google-genai';
|
} from '@langchain/google-genai';
|
||||||
import { getGeminiApiKey } from '../config';
|
import { getGeminiApiKey } from '../config';
|
||||||
import { ChatModel, EmbeddingModel } from '.';
|
import { ChatModel, EmbeddingModel } from '.';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'gemini',
|
|
||||||
displayName: 'Google Gemini',
|
|
||||||
};
|
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import { Embeddings } from '@langchain/core/embeddings';
|
import { Embeddings } from '@langchain/core/embeddings';
|
||||||
|
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { ChatOpenAI } from '@langchain/openai';
|
import { ChatOpenAI } from '@langchain/openai';
|
||||||
import { getGroqApiKey } from '../config';
|
import { getGroqApiKey } from '../config';
|
||||||
import { ChatModel } from '.';
|
import { ChatModel } from '.';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'groq',
|
|
||||||
displayName: 'Groq',
|
|
||||||
};
|
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
|
|
||||||
const groqChatModels: Record<string, string>[] = [
|
const groqChatModels: Record<string, string>[] = [
|
||||||
|
@ -1,60 +1,18 @@
|
|||||||
import { Embeddings } from '@langchain/core/embeddings';
|
import { Embeddings } from '@langchain/core/embeddings';
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import {
|
import { loadOpenAIChatModels, loadOpenAIEmbeddingModels } from './openai';
|
||||||
loadOpenAIChatModels,
|
|
||||||
loadOpenAIEmbeddingModels,
|
|
||||||
PROVIDER_INFO as OpenAIInfo,
|
|
||||||
PROVIDER_INFO,
|
|
||||||
} from './openai';
|
|
||||||
import {
|
import {
|
||||||
getCustomOpenaiApiKey,
|
getCustomOpenaiApiKey,
|
||||||
getCustomOpenaiApiUrl,
|
getCustomOpenaiApiUrl,
|
||||||
getCustomOpenaiModelName,
|
getCustomOpenaiModelName,
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { ChatOpenAI } from '@langchain/openai';
|
import { ChatOpenAI } from '@langchain/openai';
|
||||||
import {
|
import { loadOllamaChatModels, loadOllamaEmbeddingModels } from './ollama';
|
||||||
loadOllamaChatModels,
|
import { loadGroqChatModels } from './groq';
|
||||||
loadOllamaEmbeddingModels,
|
import { loadAnthropicChatModels } from './anthropic';
|
||||||
PROVIDER_INFO as OllamaInfo,
|
import { loadGeminiChatModels, loadGeminiEmbeddingModels } from './gemini';
|
||||||
} from './ollama';
|
import { loadTransformersEmbeddingsModels } from './transformers';
|
||||||
import { loadGroqChatModels, PROVIDER_INFO as GroqInfo } from './groq';
|
import { loadDeepseekChatModels } from './deepseek';
|
||||||
import {
|
|
||||||
loadAnthropicChatModels,
|
|
||||||
PROVIDER_INFO as AnthropicInfo,
|
|
||||||
} from './anthropic';
|
|
||||||
import {
|
|
||||||
loadGeminiChatModels,
|
|
||||||
loadGeminiEmbeddingModels,
|
|
||||||
PROVIDER_INFO as GeminiInfo,
|
|
||||||
} from './gemini';
|
|
||||||
import {
|
|
||||||
loadTransformersEmbeddingsModels,
|
|
||||||
PROVIDER_INFO as TransformersInfo,
|
|
||||||
} from './transformers';
|
|
||||||
import {
|
|
||||||
loadDeepseekChatModels,
|
|
||||||
PROVIDER_INFO as DeepseekInfo,
|
|
||||||
} from './deepseek';
|
|
||||||
import {
|
|
||||||
loadLMStudioChatModels,
|
|
||||||
loadLMStudioEmbeddingsModels,
|
|
||||||
PROVIDER_INFO as LMStudioInfo,
|
|
||||||
} from './lmstudio';
|
|
||||||
|
|
||||||
export const PROVIDER_METADATA = {
|
|
||||||
openai: OpenAIInfo,
|
|
||||||
ollama: OllamaInfo,
|
|
||||||
groq: GroqInfo,
|
|
||||||
anthropic: AnthropicInfo,
|
|
||||||
gemini: GeminiInfo,
|
|
||||||
transformers: TransformersInfo,
|
|
||||||
deepseek: DeepseekInfo,
|
|
||||||
lmstudio: LMStudioInfo,
|
|
||||||
custom_openai: {
|
|
||||||
key: 'custom_openai',
|
|
||||||
displayName: 'Custom OpenAI',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ChatModel {
|
export interface ChatModel {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@ -76,7 +34,6 @@ export const chatModelProviders: Record<
|
|||||||
anthropic: loadAnthropicChatModels,
|
anthropic: loadAnthropicChatModels,
|
||||||
gemini: loadGeminiChatModels,
|
gemini: loadGeminiChatModels,
|
||||||
deepseek: loadDeepseekChatModels,
|
deepseek: loadDeepseekChatModels,
|
||||||
lmstudio: loadLMStudioChatModels,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const embeddingModelProviders: Record<
|
export const embeddingModelProviders: Record<
|
||||||
@ -87,7 +44,6 @@ export const embeddingModelProviders: Record<
|
|||||||
ollama: loadOllamaEmbeddingModels,
|
ollama: loadOllamaEmbeddingModels,
|
||||||
gemini: loadGeminiEmbeddingModels,
|
gemini: loadGeminiEmbeddingModels,
|
||||||
transformers: loadTransformersEmbeddingsModels,
|
transformers: loadTransformersEmbeddingsModels,
|
||||||
lmstudio: loadLMStudioEmbeddingsModels,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAvailableChatModelProviders = async () => {
|
export const getAvailableChatModelProviders = async () => {
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
import { getKeepAlive, getLMStudioApiEndpoint } from '../config';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { ChatModel, EmbeddingModel } from '.';
|
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'lmstudio',
|
|
||||||
displayName: 'LM Studio',
|
|
||||||
};
|
|
||||||
import { ChatOpenAI } from '@langchain/openai';
|
|
||||||
import { OpenAIEmbeddings } from '@langchain/openai';
|
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
||||||
import { Embeddings } from '@langchain/core/embeddings';
|
|
||||||
|
|
||||||
interface LMStudioModel {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureV1Endpoint = (endpoint: string): string =>
|
|
||||||
endpoint.endsWith('/v1') ? endpoint : `${endpoint}/v1`;
|
|
||||||
|
|
||||||
const checkServerAvailability = async (endpoint: string): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
await axios.get(`${ensureV1Endpoint(endpoint)}/models`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loadLMStudioChatModels = async () => {
|
|
||||||
const endpoint = getLMStudioApiEndpoint();
|
|
||||||
|
|
||||||
if (!endpoint) return {};
|
|
||||||
if (!(await checkServerAvailability(endpoint))) return {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${ensureV1Endpoint(endpoint)}/models`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const chatModels: Record<string, ChatModel> = {};
|
|
||||||
|
|
||||||
response.data.data.forEach((model: LMStudioModel) => {
|
|
||||||
chatModels[model.id] = {
|
|
||||||
displayName: model.name || model.id,
|
|
||||||
model: new ChatOpenAI({
|
|
||||||
openAIApiKey: 'lm-studio',
|
|
||||||
configuration: {
|
|
||||||
baseURL: ensureV1Endpoint(endpoint),
|
|
||||||
},
|
|
||||||
modelName: model.id,
|
|
||||||
temperature: 0.7,
|
|
||||||
streaming: true,
|
|
||||||
maxRetries: 3,
|
|
||||||
}) as unknown as BaseChatModel,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return chatModels;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error loading LM Studio models: ${err}`);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loadLMStudioEmbeddingsModels = async () => {
|
|
||||||
const endpoint = getLMStudioApiEndpoint();
|
|
||||||
|
|
||||||
if (!endpoint) return {};
|
|
||||||
if (!(await checkServerAvailability(endpoint))) return {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${ensureV1Endpoint(endpoint)}/models`, {
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const embeddingsModels: Record<string, EmbeddingModel> = {};
|
|
||||||
|
|
||||||
response.data.data.forEach((model: LMStudioModel) => {
|
|
||||||
embeddingsModels[model.id] = {
|
|
||||||
displayName: model.name || model.id,
|
|
||||||
model: new OpenAIEmbeddings({
|
|
||||||
openAIApiKey: 'lm-studio',
|
|
||||||
configuration: {
|
|
||||||
baseURL: ensureV1Endpoint(endpoint),
|
|
||||||
},
|
|
||||||
modelName: model.id,
|
|
||||||
}) as unknown as Embeddings,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return embeddingsModels;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error loading LM Studio embeddings model: ${err}`);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,11 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getKeepAlive, getOllamaApiEndpoint } from '../config';
|
import { getKeepAlive, getOllamaApiEndpoint } from '../config';
|
||||||
import { ChatModel, EmbeddingModel } from '.';
|
import { ChatModel, EmbeddingModel } from '.';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'ollama',
|
|
||||||
displayName: 'Ollama',
|
|
||||||
};
|
|
||||||
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
||||||
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
|
||||||
|
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
|
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
|
||||||
import { getOpenaiApiKey } from '../config';
|
import { getOpenaiApiKey } from '../config';
|
||||||
import { ChatModel, EmbeddingModel } from '.';
|
import { ChatModel, EmbeddingModel } from '.';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'openai',
|
|
||||||
displayName: 'OpenAI',
|
|
||||||
};
|
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import { Embeddings } from '@langchain/core/embeddings';
|
import { Embeddings } from '@langchain/core/embeddings';
|
||||||
|
|
||||||
@ -30,18 +25,6 @@ const openaiChatModels: Record<string, string>[] = [
|
|||||||
displayName: 'GPT-4 omni mini',
|
displayName: 'GPT-4 omni mini',
|
||||||
key: 'gpt-4o-mini',
|
key: 'gpt-4o-mini',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
displayName: 'GPT 4.1 nano',
|
|
||||||
key: 'gpt-4.1-nano',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'GPT 4.1 mini',
|
|
||||||
key: 'gpt-4.1-mini',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'GPT 4.1',
|
|
||||||
key: 'gpt-4.1',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const openaiEmbeddingModels: Record<string, string>[] = [
|
const openaiEmbeddingModels: Record<string, string>[] = [
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import { HuggingFaceTransformersEmbeddings } from '../huggingfaceTransformer';
|
import { HuggingFaceTransformersEmbeddings } from '../huggingfaceTransformer';
|
||||||
|
|
||||||
export const PROVIDER_INFO = {
|
|
||||||
key: 'transformers',
|
|
||||||
displayName: 'Hugging Face',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loadTransformersEmbeddingsModels = async () => {
|
export const loadTransformersEmbeddingsModels = async () => {
|
||||||
try {
|
try {
|
||||||
const embeddingModels = {
|
const embeddingModels = {
|
||||||
|
@ -64,7 +64,7 @@ export const getDocumentsFromLinks = async ({ links }: { links: string[] }) => {
|
|||||||
const splittedText = await splitter.splitText(parsedText);
|
const splittedText = await splitter.splitText(parsedText);
|
||||||
const title = res.data
|
const title = res.data
|
||||||
.toString('utf8')
|
.toString('utf8')
|
||||||
.match(/<title.*>(.*?)<\/title>/)?.[1];
|
.match(/<title>(.*?)<\/title>/)?.[1];
|
||||||
|
|
||||||
const linkDocs = splittedText.map((text) => {
|
const linkDocs = splittedText.map((text) => {
|
||||||
return new Document({
|
return new Document({
|
||||||
|
Reference in New Issue
Block a user