mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-09-13 13:01:33 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -12,6 +12,9 @@ COPY public ./public
|
|||||||
RUN mkdir -p /home/perplexica/data
|
RUN mkdir -p /home/perplexica/data
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
|
RUN yarn add --dev @vercel/ncc
|
||||||
|
RUN yarn ncc build ./src/lib/db/migrate.ts -o migrator
|
||||||
|
|
||||||
FROM node:20.18.0-slim
|
FROM node:20.18.0-slim
|
||||||
|
|
||||||
WORKDIR /home/perplexica
|
WORKDIR /home/perplexica
|
||||||
@@ -21,7 +24,12 @@ COPY --from=builder /home/perplexica/.next/static ./public/_next/static
|
|||||||
|
|
||||||
COPY --from=builder /home/perplexica/.next/standalone ./
|
COPY --from=builder /home/perplexica/.next/standalone ./
|
||||||
COPY --from=builder /home/perplexica/data ./data
|
COPY --from=builder /home/perplexica/data ./data
|
||||||
|
COPY drizzle ./drizzle
|
||||||
|
COPY --from=builder /home/perplexica/migrator/build ./build
|
||||||
|
COPY --from=builder /home/perplexica/migrator/index.js ./migrate.js
|
||||||
|
|
||||||
RUN mkdir /home/perplexica/uploads
|
RUN mkdir /home/perplexica/uploads
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
COPY entrypoint.sh ./entrypoint.sh
|
||||||
|
RUN chmod +x ./entrypoint.sh
|
||||||
|
CMD ["./entrypoint.sh"]
|
@@ -16,6 +16,7 @@ services:
|
|||||||
dockerfile: app.dockerfile
|
dockerfile: app.dockerfile
|
||||||
environment:
|
environment:
|
||||||
- SEARXNG_API_URL=http://searxng:8080
|
- SEARXNG_API_URL=http://searxng:8080
|
||||||
|
- DATA_DIR=/home/perplexica
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { defineConfig } from 'drizzle-kit';
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
schema: './src/lib/db/schema.ts',
|
schema: './src/lib/db/schema.ts',
|
||||||
out: './drizzle',
|
out: './drizzle',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: './data/db.sqlite',
|
url: path.join(process.cwd(), 'data', 'db.sqlite'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
16
drizzle/0000_fuzzy_randall.sql
Normal file
16
drizzle/0000_fuzzy_randall.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `chats` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`title` text NOT NULL,
|
||||||
|
`createdAt` text NOT NULL,
|
||||||
|
`focusMode` text NOT NULL,
|
||||||
|
`files` text DEFAULT '[]'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS `messages` (
|
||||||
|
`id` integer PRIMARY KEY NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`chatId` text NOT NULL,
|
||||||
|
`messageId` text NOT NULL,
|
||||||
|
`type` text,
|
||||||
|
`metadata` text
|
||||||
|
);
|
116
drizzle/meta/0000_snapshot.json
Normal file
116
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "ef3a044b-0f34-40b5-babb-2bb3a909ba27",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"chats": {
|
||||||
|
"name": "chats",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"focusMode": {
|
||||||
|
"name": "focusMode",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"name": "files",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"name": "messages",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"chatId": {
|
||||||
|
"name": "chatId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"messageId": {
|
||||||
|
"name": "messageId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1748405503809,
|
||||||
|
"tag": "0000_fuzzy_randall",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
entrypoint.sh
Normal file
6
entrypoint.sh
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
node migrate.js
|
||||||
|
|
||||||
|
exec node server.js
|
@@ -25,5 +25,8 @@ 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,6 +8,7 @@ import {
|
|||||||
getOllamaApiEndpoint,
|
getOllamaApiEndpoint,
|
||||||
getOpenaiApiKey,
|
getOpenaiApiKey,
|
||||||
getDeepseekApiKey,
|
getDeepseekApiKey,
|
||||||
|
getLMStudioApiEndpoint,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
} from '@/lib/config';
|
} from '@/lib/config';
|
||||||
import {
|
import {
|
||||||
@@ -51,6 +52,7 @@ 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();
|
||||||
@@ -93,6 +95,9 @@ 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,
|
||||||
|
@@ -7,6 +7,7 @@ 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: {
|
||||||
@@ -20,6 +21,7 @@ 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;
|
||||||
@@ -141,7 +143,7 @@ const Page = () => {
|
|||||||
const [selectedEmbeddingModel, setSelectedEmbeddingModel] = useState<
|
const [selectedEmbeddingModel, setSelectedEmbeddingModel] = useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
|
const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
|
||||||
const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
|
const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
|
||||||
const [systemInstructions, setSystemInstructions] = useState<string>('');
|
const [systemInstructions, setSystemInstructions] = useState<string>('');
|
||||||
@@ -149,7 +151,6 @@ const Page = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
setIsLoading(true);
|
|
||||||
const res = await fetch(`/api/config`, {
|
const res = await fetch(`/api/config`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -548,8 +549,9 @@ 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),
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -690,8 +692,9 @@ 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),
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -858,6 +861,25 @@ 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>
|
||||||
|
@@ -1,8 +1,122 @@
|
|||||||
import { Clock, Edit, Share, Trash } from 'lucide-react';
|
import { Clock, Edit, Share, Trash, FileText, FileDown } from 'lucide-react';
|
||||||
import { Message } from './ChatWindow';
|
import { Message } from './ChatWindow';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, Fragment } from 'react';
|
||||||
import { formatTimeDifference } from '@/lib/utils';
|
import { formatTimeDifference } from '@/lib/utils';
|
||||||
import DeleteChat from './DeleteChat';
|
import DeleteChat from './DeleteChat';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverButton,
|
||||||
|
PopoverPanel,
|
||||||
|
Transition,
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import jsPDF from 'jspdf';
|
||||||
|
|
||||||
|
const downloadFile = (filename: string, content: string, type: string) => {
|
||||||
|
const blob = new Blob([content], { type });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportAsMarkdown = (messages: Message[], title: string) => {
|
||||||
|
const date = new Date(messages[0]?.createdAt || Date.now()).toLocaleString();
|
||||||
|
let md = `# 💬 Chat Export: ${title}\n\n`;
|
||||||
|
md += `*Exported on: ${date}*\n\n---\n`;
|
||||||
|
messages.forEach((msg, idx) => {
|
||||||
|
md += `\n---\n`;
|
||||||
|
md += `**${msg.role === 'user' ? '🧑 User' : '🤖 Assistant'}**
|
||||||
|
`;
|
||||||
|
md += `*${new Date(msg.createdAt).toLocaleString()}*\n\n`;
|
||||||
|
md += `> ${msg.content.replace(/\n/g, '\n> ')}\n`;
|
||||||
|
if (msg.sources && msg.sources.length > 0) {
|
||||||
|
md += `\n**Citations:**\n`;
|
||||||
|
msg.sources.forEach((src: any, i: number) => {
|
||||||
|
const url = src.metadata?.url || '';
|
||||||
|
md += `- [${i + 1}] [${url}](${url})\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
md += '\n---\n';
|
||||||
|
downloadFile(`${title || 'chat'}.md`, md, 'text/markdown');
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportAsPDF = (messages: Message[], title: string) => {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
const date = new Date(messages[0]?.createdAt || Date.now()).toLocaleString();
|
||||||
|
let y = 15;
|
||||||
|
const pageHeight = doc.internal.pageSize.height;
|
||||||
|
doc.setFontSize(18);
|
||||||
|
doc.text(`Chat Export: ${title}`, 10, y);
|
||||||
|
y += 8;
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text(`Exported on: ${date}`, 10, y);
|
||||||
|
y += 8;
|
||||||
|
doc.setDrawColor(200);
|
||||||
|
doc.line(10, y, 200, y);
|
||||||
|
y += 6;
|
||||||
|
doc.setTextColor(30);
|
||||||
|
messages.forEach((msg, idx) => {
|
||||||
|
if (y > pageHeight - 30) {
|
||||||
|
doc.addPage();
|
||||||
|
y = 15;
|
||||||
|
}
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text(`${msg.role === 'user' ? 'User' : 'Assistant'}`, 10, y);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.setTextColor(120);
|
||||||
|
doc.text(`${new Date(msg.createdAt).toLocaleString()}`, 40, y);
|
||||||
|
y += 6;
|
||||||
|
doc.setTextColor(30);
|
||||||
|
doc.setFontSize(12);
|
||||||
|
const lines = doc.splitTextToSize(msg.content, 180);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (y > pageHeight - 20) {
|
||||||
|
doc.addPage();
|
||||||
|
y = 15;
|
||||||
|
}
|
||||||
|
doc.text(lines[i], 12, y);
|
||||||
|
y += 6;
|
||||||
|
}
|
||||||
|
if (msg.sources && msg.sources.length > 0) {
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setTextColor(80);
|
||||||
|
if (y > pageHeight - 20) {
|
||||||
|
doc.addPage();
|
||||||
|
y = 15;
|
||||||
|
}
|
||||||
|
doc.text('Citations:', 12, y);
|
||||||
|
y += 5;
|
||||||
|
msg.sources.forEach((src: any, i: number) => {
|
||||||
|
const url = src.metadata?.url || '';
|
||||||
|
if (y > pageHeight - 15) {
|
||||||
|
doc.addPage();
|
||||||
|
y = 15;
|
||||||
|
}
|
||||||
|
doc.text(`- [${i + 1}] ${url}`, 15, y);
|
||||||
|
y += 5;
|
||||||
|
});
|
||||||
|
doc.setTextColor(30);
|
||||||
|
}
|
||||||
|
y += 6;
|
||||||
|
doc.setDrawColor(230);
|
||||||
|
if (y > pageHeight - 10) {
|
||||||
|
doc.addPage();
|
||||||
|
y = 15;
|
||||||
|
}
|
||||||
|
doc.line(10, y, 200, y);
|
||||||
|
y += 4;
|
||||||
|
});
|
||||||
|
doc.save(`${title || 'chat'}.pdf`);
|
||||||
|
};
|
||||||
|
|
||||||
const Navbar = ({
|
const Navbar = ({
|
||||||
chatId,
|
chatId,
|
||||||
@@ -59,10 +173,39 @@ const Navbar = ({
|
|||||||
<p className="hidden lg:flex">{title}</p>
|
<p className="hidden lg:flex">{title}</p>
|
||||||
|
|
||||||
<div className="flex flex-row items-center space-x-4">
|
<div className="flex flex-row items-center space-x-4">
|
||||||
<Share
|
<Popover className="relative">
|
||||||
size={17}
|
<PopoverButton className="active:scale-95 transition duration-100 cursor-pointer p-2 rounded-full hover:bg-light-secondary dark:hover:bg-dark-secondary">
|
||||||
className="active:scale-95 transition duration-100 cursor-pointer"
|
<Share size={17} />
|
||||||
/>
|
</PopoverButton>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="opacity-0 translate-y-1"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
|
leaveTo="opacity-0 translate-y-1"
|
||||||
|
>
|
||||||
|
<PopoverPanel className="absolute right-0 mt-2 w-64 rounded-xl shadow-xl bg-light-primary dark:bg-dark-primary border border-light-200 dark:border-dark-200 z-50">
|
||||||
|
<div className="flex flex-col py-3 px-3 gap-2">
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-left hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors text-black dark:text-white rounded-lg font-medium"
|
||||||
|
onClick={() => exportAsMarkdown(messages, title || '')}
|
||||||
|
>
|
||||||
|
<FileText size={17} className="text-[#24A0ED]" />
|
||||||
|
Export as Markdown
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-left hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors text-black dark:text-white rounded-lg font-medium"
|
||||||
|
onClick={() => exportAsPDF(messages, title || '')}
|
||||||
|
>
|
||||||
|
<FileDown size={17} className="text-[#24A0ED]" />
|
||||||
|
Export as PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</PopoverPanel>
|
||||||
|
</Transition>
|
||||||
|
</Popover>
|
||||||
<DeleteChat redirect chatId={chatId} chats={[]} setChats={() => {}} />
|
<DeleteChat redirect chatId={chatId} chats={[]} setChats={() => {}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,14 @@
|
|||||||
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 {
|
||||||
@@ -28,6 +35,9 @@ 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;
|
||||||
@@ -43,10 +53,17 @@ type RecursivePartial<T> = {
|
|||||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadConfig = () =>
|
const loadConfig = () => {
|
||||||
toml.parse(
|
// Server-side only
|
||||||
fs.readFileSync(path.join(process.cwd(), `${configFileName}`), 'utf-8'),
|
if (typeof window === 'undefined') {
|
||||||
) as any as Config;
|
return toml.parse(
|
||||||
|
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;
|
||||||
@@ -77,6 +94,9 @@ 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;
|
||||||
@@ -109,10 +129,13 @@ const mergeConfigs = (current: any, update: any): any => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const updateConfig = (config: RecursivePartial<Config>) => {
|
export const updateConfig = (config: RecursivePartial<Config>) => {
|
||||||
const currentConfig = loadConfig();
|
// Server-side only
|
||||||
const mergedConfig = mergeConfigs(currentConfig, config);
|
if (typeof window === 'undefined') {
|
||||||
fs.writeFileSync(
|
const currentConfig = loadConfig();
|
||||||
path.join(path.join(process.cwd(), `${configFileName}`)),
|
const mergedConfig = mergeConfigs(currentConfig, config);
|
||||||
toml.stringify(mergedConfig),
|
fs.writeFileSync(
|
||||||
);
|
path.join(path.join(process.cwd(), `${configFileName}`)),
|
||||||
|
toml.stringify(mergedConfig),
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@@ -3,7 +3,8 @@ import Database from 'better-sqlite3';
|
|||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
const sqlite = new Database(path.join(process.cwd(), 'data/db.sqlite'));
|
const DATA_DIR = process.env.DATA_DIR || process.cwd();
|
||||||
|
const sqlite = new Database(path.join(DATA_DIR, './data/db.sqlite'));
|
||||||
const db = drizzle(sqlite, {
|
const db = drizzle(sqlite, {
|
||||||
schema: schema,
|
schema: schema,
|
||||||
});
|
});
|
||||||
|
5
src/lib/db/migrate.ts
Normal file
5
src/lib/db/migrate.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import db from './';
|
||||||
|
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
migrate(db, { migrationsFolder: path.join(process.cwd(), 'drizzle') });
|
@@ -1,6 +1,11 @@
|
|||||||
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,6 +3,11 @@ 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,6 +4,11 @@ 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,101 +1,36 @@
|
|||||||
import { ChatOpenAI } from '@langchain/openai';
|
import { ChatOpenAI } from '@langchain/openai';
|
||||||
import { getGroqApiKey } from '../config';
|
import { getGroqApiKey } from '../config';
|
||||||
import { ChatModel } from '.';
|
import { ChatModel } from '.';
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
||||||
|
|
||||||
const groqChatModels: Record<string, string>[] = [
|
export const PROVIDER_INFO = {
|
||||||
{
|
key: 'groq',
|
||||||
displayName: 'Gemma2 9B IT',
|
displayName: 'Groq',
|
||||||
key: 'gemma2-9b-it',
|
};
|
||||||
},
|
|
||||||
{
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
displayName: 'Llama 3.3 70B Versatile',
|
|
||||||
key: 'llama-3.3-70b-versatile',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.1 8B Instant',
|
|
||||||
key: 'llama-3.1-8b-instant',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama3 70B 8192',
|
|
||||||
key: 'llama3-70b-8192',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama3 8B 8192',
|
|
||||||
key: 'llama3-8b-8192',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Mixtral 8x7B 32768',
|
|
||||||
key: 'mixtral-8x7b-32768',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Qwen QWQ 32B (Preview)',
|
|
||||||
key: 'qwen-qwq-32b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Mistral Saba 24B (Preview)',
|
|
||||||
key: 'mistral-saba-24b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Qwen 2.5 Coder 32B (Preview)',
|
|
||||||
key: 'qwen-2.5-coder-32b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Qwen 2.5 32B (Preview)',
|
|
||||||
key: 'qwen-2.5-32b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'DeepSeek R1 Distill Qwen 32B (Preview)',
|
|
||||||
key: 'deepseek-r1-distill-qwen-32b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'DeepSeek R1 Distill Llama 70B (Preview)',
|
|
||||||
key: 'deepseek-r1-distill-llama-70b',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.3 70B SpecDec (Preview)',
|
|
||||||
key: 'llama-3.3-70b-specdec',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.2 1B Preview (Preview)',
|
|
||||||
key: 'llama-3.2-1b-preview',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.2 3B Preview (Preview)',
|
|
||||||
key: 'llama-3.2-3b-preview',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.2 11B Vision Preview (Preview)',
|
|
||||||
key: 'llama-3.2-11b-vision-preview',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Llama 3.2 90B Vision Preview (Preview)',
|
|
||||||
key: 'llama-3.2-90b-vision-preview',
|
|
||||||
},
|
|
||||||
/* {
|
|
||||||
displayName: 'Llama 4 Maverick 17B 128E Instruct (Preview)',
|
|
||||||
key: 'meta-llama/llama-4-maverick-17b-128e-instruct',
|
|
||||||
}, */
|
|
||||||
{
|
|
||||||
displayName: 'Llama 4 Scout 17B 16E Instruct (Preview)',
|
|
||||||
key: 'meta-llama/llama-4-scout-17b-16e-instruct',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const loadGroqChatModels = async () => {
|
export const loadGroqChatModels = async () => {
|
||||||
const groqApiKey = getGroqApiKey();
|
const groqApiKey = getGroqApiKey();
|
||||||
|
|
||||||
if (!groqApiKey) return {};
|
if (!groqApiKey) return {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const res = await fetch('https://api.groq.com/openai/v1/models', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `bearer ${groqApiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const groqChatModels = (await res.json()).data;
|
||||||
const chatModels: Record<string, ChatModel> = {};
|
const chatModels: Record<string, ChatModel> = {};
|
||||||
|
|
||||||
groqChatModels.forEach((model) => {
|
groqChatModels.forEach((model: any) => {
|
||||||
chatModels[model.key] = {
|
chatModels[model.id] = {
|
||||||
displayName: model.displayName,
|
displayName: model.id,
|
||||||
model: new ChatOpenAI({
|
model: new ChatOpenAI({
|
||||||
openAIApiKey: groqApiKey,
|
openAIApiKey: groqApiKey,
|
||||||
modelName: model.key,
|
modelName: model.id,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: 'https://api.groq.com/openai/v1',
|
baseURL: 'https://api.groq.com/openai/v1',
|
||||||
|
@@ -1,18 +1,60 @@
|
|||||||
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 { loadOpenAIChatModels, loadOpenAIEmbeddingModels } from './openai';
|
import {
|
||||||
|
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 { loadOllamaChatModels, loadOllamaEmbeddingModels } from './ollama';
|
import {
|
||||||
import { loadGroqChatModels } from './groq';
|
loadOllamaChatModels,
|
||||||
import { loadAnthropicChatModels } from './anthropic';
|
loadOllamaEmbeddingModels,
|
||||||
import { loadGeminiChatModels, loadGeminiEmbeddingModels } from './gemini';
|
PROVIDER_INFO as OllamaInfo,
|
||||||
import { loadTransformersEmbeddingsModels } from './transformers';
|
} from './ollama';
|
||||||
import { loadDeepseekChatModels } from './deepseek';
|
import { loadGroqChatModels, PROVIDER_INFO as GroqInfo } from './groq';
|
||||||
|
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;
|
||||||
@@ -34,6 +76,7 @@ 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<
|
||||||
@@ -44,6 +87,7 @@ 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 () => {
|
||||||
|
100
src/lib/providers/lmstudio.ts
Normal file
100
src/lib/providers/lmstudio.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
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,6 +1,11 @@
|
|||||||
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,6 +1,11 @@
|
|||||||
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';
|
||||||
|
|
||||||
@@ -25,6 +30,18 @@ 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,5 +1,10 @@
|
|||||||
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