chore: Update dependencies and fix import paths
This commit is contained in:
parent
3b737a078a
commit
81c5e30fda
46 changed files with 1626 additions and 371 deletions
.eslintrc.jsonpackage.jsontsconfig.json
src
agents
academicSearchAgent.tsimageSearchAgent.tsredditSearchAgent.tsvideoSearchAgent.tswebSearchAgent.tswolframAlphaSearchAgent.tswritingAssistant.tsyoutubeSearchAgent.ts
app.tsconfig.tsdb
lib
routes
websocket
ui
.eslintrc.json
yarn.lockapp/library
components
Chat.tsxChatWindow.tsxDeleteChat.tsxEmptyChatMessageInput.tsx
MessageActions
MessageBox.tsxMessageInput.tsxMessageInputActions
MessageSources.tsxNavbar.tsxSearchImages.tsxSearchVideos.tsxSettingsDialog.tsxSidebar.tsxlib
|
@ -3,11 +3,30 @@
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:prettier/recommended"
|
"plugin:prettier/recommended",
|
||||||
|
"plugin:unicorn/recommended"
|
||||||
],
|
],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@typescript-eslint",
|
"@typescript-eslint",
|
||||||
"prettier"
|
"prettier",
|
||||||
|
"unicorn"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"unicorn/filename-case": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"cases": {
|
||||||
|
"camelCase": true,
|
||||||
|
"pascalCase": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unicorn/prevent-abbreviations": "warn",
|
||||||
|
"unicorn/no-null": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -21,6 +21,9 @@
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
|
"eslint-plugin-react": "^7.34.3",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-unicorn": "^54.0.0",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import computeSimilarity from "../utils/computeSimilarity";
|
import computeSimilarity from "../utils/computeSimilarity";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ const basicAcademicSearchResponsePrompt = `
|
||||||
Anything between the \`context\` is retrieved from a search engine and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
Anything between the \`context\` is retrieved from a search engine and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -80,7 +80,7 @@ const createBasicAcademicSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(basicAcademicSearchRetrieverPrompt),
|
PromptTemplate.fromTemplate(basicAcademicSearchRetrieverPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
if (input === "not_needed") {
|
if (input === "not_needed") {
|
||||||
return { query: "", docs: [] };
|
return { query: "", docs: [] };
|
||||||
|
@ -108,30 +108,30 @@ const createBasicAcademicSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processDocs = async (docs: Document[]) => {
|
||||||
|
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
const createBasicAcademicSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
const createBasicAcademicSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||||
const basicAcademicSearchRetrieverChain = createBasicAcademicSearchRetrieverChain(llm);
|
const basicAcademicSearchRetrieverChain = createBasicAcademicSearchRetrieverChain(llm);
|
||||||
|
|
||||||
const processDocs = async (docs: Document[]) => {
|
|
||||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||||
if (docs.length === 0) {
|
if (docs.length === 0) {
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
const docsWithContent = docs.filter(document => document.pageContent && document.pageContent.length > 0);
|
||||||
|
|
||||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
const [documentEmbeddings, queryEmbedding] = await Promise.all([
|
||||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
embeddings.embedDocuments(docsWithContent.map(document => document.pageContent)),
|
||||||
embeddings.embedQuery(query),
|
embeddings.embedQuery(query),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const similarity = docEmbeddings.map((docEmbedding, i) => {
|
const similarity = documentEmbeddings.map((documentEmbedding, index) => {
|
||||||
const sim = computeSimilarity(queryEmbedding, docEmbedding);
|
const sim = computeSimilarity(queryEmbedding, documentEmbedding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: i,
|
index: index,
|
||||||
similarity: sim,
|
similarity: sim,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -167,7 +167,7 @@ const createBasicAcademicSearchAnsweringChain = (llm: BaseChatModel, embeddings:
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -190,9 +190,9 @@ const basicAcademicSearch = (query: string, history: BaseMessage[], llm: BaseCha
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in academic search: ${err}`);
|
logger.error(`Error in academic search: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -32,7 +32,7 @@ type ImageSearchChainInput = {
|
||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const createImageSearchChain = (llm: BaseChatModel) => {
|
const createImageSearchChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
|
@ -46,7 +46,7 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
||||||
}),
|
}),
|
||||||
PromptTemplate.fromTemplate(imageSearchChainPrompt),
|
PromptTemplate.fromTemplate(imageSearchChainPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
const res = await searchSearxng(input, {
|
const res = await searchSearxng(input, {
|
||||||
engines: ["bing images", "google images"],
|
engines: ["bing images", "google images"],
|
||||||
|
@ -54,7 +54,7 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
||||||
|
|
||||||
const images = [];
|
const images = [];
|
||||||
|
|
||||||
res.results.forEach(result => {
|
for (const result of res.results) {
|
||||||
if (result.img_src && result.url && result.title) {
|
if (result.img_src && result.url && result.title) {
|
||||||
images.push({
|
images.push({
|
||||||
img_src: result.img_src,
|
img_src: result.img_src,
|
||||||
|
@ -62,7 +62,7 @@ const createImageSearchChain = (llm: BaseChatModel) => {
|
||||||
title: result.title,
|
title: result.title,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return images.slice(0, 10);
|
return images.slice(0, 10);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import computeSimilarity from "../utils/computeSimilarity";
|
import computeSimilarity from "../utils/computeSimilarity";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ const basicRedditSearchResponsePrompt = `
|
||||||
Anything between the \`context\` is retrieved from Reddit and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
Anything between the \`context\` is retrieved from Reddit and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -80,7 +80,7 @@ const createBasicRedditSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(basicRedditSearchRetrieverPrompt),
|
PromptTemplate.fromTemplate(basicRedditSearchRetrieverPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
if (input === "not_needed") {
|
if (input === "not_needed") {
|
||||||
return { query: "", docs: [] };
|
return { query: "", docs: [] };
|
||||||
|
@ -94,7 +94,7 @@ const createBasicRedditSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
const documents = res.results.map(
|
const documents = res.results.map(
|
||||||
result =>
|
result =>
|
||||||
new Document({
|
new Document({
|
||||||
pageContent: result.content ? result.content : result.title,
|
pageContent: result.content ?? result.title,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: result.title,
|
title: result.title,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
|
@ -108,30 +108,30 @@ const createBasicRedditSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processDocs = async (docs: Document[]) => {
|
||||||
|
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
const createBasicRedditSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
const createBasicRedditSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||||
const basicRedditSearchRetrieverChain = createBasicRedditSearchRetrieverChain(llm);
|
const basicRedditSearchRetrieverChain = createBasicRedditSearchRetrieverChain(llm);
|
||||||
|
|
||||||
const processDocs = async (docs: Document[]) => {
|
|
||||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||||
if (docs.length === 0) {
|
if (docs.length === 0) {
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
const docsWithContent = docs.filter(document => document.pageContent && document.pageContent.length > 0);
|
||||||
|
|
||||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
const [documentEmbeddings, queryEmbedding] = await Promise.all([
|
||||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
embeddings.embedDocuments(docsWithContent.map(document => document.pageContent)),
|
||||||
embeddings.embedQuery(query),
|
embeddings.embedQuery(query),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const similarity = docEmbeddings.map((docEmbedding, i) => {
|
const similarity = documentEmbeddings.map((documentEmbedding, index) => {
|
||||||
const sim = computeSimilarity(queryEmbedding, docEmbedding);
|
const sim = computeSimilarity(queryEmbedding, documentEmbedding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: i,
|
index: index,
|
||||||
similarity: sim,
|
similarity: sim,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -168,7 +168,7 @@ const createBasicRedditSearchAnsweringChain = (llm: BaseChatModel, embeddings: E
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -190,9 +190,9 @@ const basicRedditSearch = (query: string, history: BaseMessage[], llm: BaseChatM
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in RedditSearch: ${err}`);
|
logger.error(`Error in RedditSearch: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -32,7 +32,7 @@ type VideoSearchChainInput = {
|
||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const createVideoSearchChain = (llm: BaseChatModel) => {
|
const createVideoSearchChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
|
@ -46,7 +46,7 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
||||||
}),
|
}),
|
||||||
PromptTemplate.fromTemplate(VideoSearchChainPrompt),
|
PromptTemplate.fromTemplate(VideoSearchChainPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
const res = await searchSearxng(input, {
|
const res = await searchSearxng(input, {
|
||||||
engines: ["youtube"],
|
engines: ["youtube"],
|
||||||
|
@ -54,7 +54,7 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
||||||
|
|
||||||
const videos = [];
|
const videos = [];
|
||||||
|
|
||||||
res.results.forEach(result => {
|
for (const result of res.results) {
|
||||||
if (result.thumbnail && result.url && result.title && result.iframe_src) {
|
if (result.thumbnail && result.url && result.title && result.iframe_src) {
|
||||||
videos.push({
|
videos.push({
|
||||||
img_src: result.thumbnail,
|
img_src: result.thumbnail,
|
||||||
|
@ -63,7 +63,7 @@ const createVideoSearchChain = (llm: BaseChatModel) => {
|
||||||
iframe_src: result.iframe_src,
|
iframe_src: result.iframe_src,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return videos.slice(0, 10);
|
return videos.slice(0, 10);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import computeSimilarity from "../utils/computeSimilarity";
|
import computeSimilarity from "../utils/computeSimilarity";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ const basicWebSearchResponsePrompt = `
|
||||||
Anything between the \`context\` is retrieved from a search engine and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
Anything between the \`context\` is retrieved from a search engine and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -80,7 +80,7 @@ const createBasicWebSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(basicSearchRetrieverPrompt),
|
PromptTemplate.fromTemplate(basicSearchRetrieverPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
if (input === "not_needed") {
|
if (input === "not_needed") {
|
||||||
return { query: "", docs: [] };
|
return { query: "", docs: [] };
|
||||||
|
@ -107,30 +107,30 @@ const createBasicWebSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processDocs = async (docs: Document[]) => {
|
||||||
|
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
const createBasicWebSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
const createBasicWebSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||||
const basicWebSearchRetrieverChain = createBasicWebSearchRetrieverChain(llm);
|
const basicWebSearchRetrieverChain = createBasicWebSearchRetrieverChain(llm);
|
||||||
|
|
||||||
const processDocs = async (docs: Document[]) => {
|
|
||||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||||
if (docs.length === 0) {
|
if (docs.length === 0) {
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
const docsWithContent = docs.filter(document => document.pageContent && document.pageContent.length > 0);
|
||||||
|
|
||||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
const [documentEmbeddings, queryEmbedding] = await Promise.all([
|
||||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
embeddings.embedDocuments(docsWithContent.map(document => document.pageContent)),
|
||||||
embeddings.embedQuery(query),
|
embeddings.embedQuery(query),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const similarity = docEmbeddings.map((docEmbedding, i) => {
|
const similarity = documentEmbeddings.map((documentEmbedding, index) => {
|
||||||
const sim = computeSimilarity(queryEmbedding, docEmbedding);
|
const sim = computeSimilarity(queryEmbedding, documentEmbedding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: i,
|
index: index,
|
||||||
similarity: sim,
|
similarity: sim,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -167,7 +167,7 @@ const createBasicWebSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embe
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -190,9 +190,9 @@ const basicWebSearch = (query: string, history: BaseMessage[], llm: BaseChatMode
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in websearch: ${err}`);
|
logger.error(`Error in websearch: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
const basicWolframAlphaSearchRetrieverPrompt = `
|
const basicWolframAlphaSearchRetrieverPrompt = `
|
||||||
|
@ -54,7 +54,7 @@ const basicWolframAlphaSearchResponsePrompt = `
|
||||||
Anything between the \`context\` is retrieved from Wolfram Alpha and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
Anything between the \`context\` is retrieved from Wolfram Alpha and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -79,7 +79,7 @@ const createBasicWolframAlphaSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(basicWolframAlphaSearchRetrieverPrompt),
|
PromptTemplate.fromTemplate(basicWolframAlphaSearchRetrieverPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
if (input === "not_needed") {
|
if (input === "not_needed") {
|
||||||
return { query: "", docs: [] };
|
return { query: "", docs: [] };
|
||||||
|
@ -107,13 +107,13 @@ const createBasicWolframAlphaSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processDocs = (docs: Document[]) => {
|
||||||
|
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
||||||
const basicWolframAlphaSearchRetrieverChain = createBasicWolframAlphaSearchRetrieverChain(llm);
|
const basicWolframAlphaSearchRetrieverChain = createBasicWolframAlphaSearchRetrieverChain(llm);
|
||||||
|
|
||||||
const processDocs = (docs: Document[]) => {
|
|
||||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
RunnableMap.from({
|
RunnableMap.from({
|
||||||
query: (input: BasicChainInput) => input.query,
|
query: (input: BasicChainInput) => input.query,
|
||||||
|
@ -139,7 +139,7 @@ const createBasicWolframAlphaSearchAnsweringChain = (llm: BaseChatModel) => {
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -161,9 +161,9 @@ const basicWolframAlphaSearch = (query: string, history: BaseMessage[], llm: Bas
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in WolframAlphaSearch: ${err}`);
|
logger.error(`Error in WolframAlphaSearch: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts
|
||||||
import { RunnableSequence } from "@langchain/core/runnables";
|
import { RunnableSequence } from "@langchain/core/runnables";
|
||||||
import { StringOutputParser } from "@langchain/core/output_parsers";
|
import { StringOutputParser } from "@langchain/core/output_parsers";
|
||||||
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
@ -13,7 +13,7 @@ You are Perplexica, an AI model who is expert at searching the web and answering
|
||||||
Since you are a writing assistant, you would not perform web searches. If you think you lack information to answer the query, you can ask the user for more information or suggest them to switch to a different focus mode.
|
Since you are a writing assistant, you would not perform web searches. If you think you lack information to answer the query, you can ask the user for more information or suggest them to switch to a different focus mode.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -34,7 +34,7 @@ const createWritingAssistantChain = (llm: BaseChatModel) => {
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -62,9 +62,9 @@ const handleWritingAssistant = (
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in writing assistant: ${err}`);
|
logger.error(`Error in writing assistant: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StreamEvent } from "@langchain/core/tracers/log_stream";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import formatChatHistoryAsString from "../utils/formatHistory";
|
import formatChatHistoryAsString from "../utils/formatHistory";
|
||||||
import eventEmitter from "events";
|
import eventEmitter from "node:events";
|
||||||
import computeSimilarity from "../utils/computeSimilarity";
|
import computeSimilarity from "../utils/computeSimilarity";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ const basicYoutubeSearchResponsePrompt = `
|
||||||
Anything between the \`context\` is retrieved from Youtube and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
Anything between the \`context\` is retrieved from Youtube and is not a part of the conversation with the user. Today's date is ${new Date().toISOString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const stringParser = new StringOutputParser();
|
||||||
|
|
||||||
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
const handleStream = async (stream: AsyncGenerator<StreamEvent, unknown, unknown>, emitter: eventEmitter) => {
|
||||||
for await (const event of stream) {
|
for await (const event of stream) {
|
||||||
|
@ -80,7 +80,7 @@ const createBasicYoutubeSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
return RunnableSequence.from([
|
return RunnableSequence.from([
|
||||||
PromptTemplate.fromTemplate(basicYoutubeSearchRetrieverPrompt),
|
PromptTemplate.fromTemplate(basicYoutubeSearchRetrieverPrompt),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
RunnableLambda.from(async (input: string) => {
|
RunnableLambda.from(async (input: string) => {
|
||||||
if (input === "not_needed") {
|
if (input === "not_needed") {
|
||||||
return { query: "", docs: [] };
|
return { query: "", docs: [] };
|
||||||
|
@ -94,7 +94,7 @@ const createBasicYoutubeSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
const documents = res.results.map(
|
const documents = res.results.map(
|
||||||
result =>
|
result =>
|
||||||
new Document({
|
new Document({
|
||||||
pageContent: result.content ? result.content : result.title,
|
pageContent: result.content ?? result.title,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: result.title,
|
title: result.title,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
|
@ -108,30 +108,30 @@ const createBasicYoutubeSearchRetrieverChain = (llm: BaseChatModel) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processDocs = async (docs: Document[]) => {
|
||||||
|
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
const createBasicYoutubeSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
const createBasicYoutubeSearchAnsweringChain = (llm: BaseChatModel, embeddings: Embeddings) => {
|
||||||
const basicYoutubeSearchRetrieverChain = createBasicYoutubeSearchRetrieverChain(llm);
|
const basicYoutubeSearchRetrieverChain = createBasicYoutubeSearchRetrieverChain(llm);
|
||||||
|
|
||||||
const processDocs = async (docs: Document[]) => {
|
|
||||||
return docs.map((_, index) => `${index + 1}. ${docs[index].pageContent}`).join("\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
const rerankDocs = async ({ query, docs }: { query: string; docs: Document[] }) => {
|
||||||
if (docs.length === 0) {
|
if (docs.length === 0) {
|
||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docsWithContent = docs.filter(doc => doc.pageContent && doc.pageContent.length > 0);
|
const docsWithContent = docs.filter(document => document.pageContent && document.pageContent.length > 0);
|
||||||
|
|
||||||
const [docEmbeddings, queryEmbedding] = await Promise.all([
|
const [documentEmbeddings, queryEmbedding] = await Promise.all([
|
||||||
embeddings.embedDocuments(docsWithContent.map(doc => doc.pageContent)),
|
embeddings.embedDocuments(docsWithContent.map(document => document.pageContent)),
|
||||||
embeddings.embedQuery(query),
|
embeddings.embedQuery(query),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const similarity = docEmbeddings.map((docEmbedding, i) => {
|
const similarity = documentEmbeddings.map((documentEmbedding, index) => {
|
||||||
const sim = computeSimilarity(queryEmbedding, docEmbedding);
|
const sim = computeSimilarity(queryEmbedding, documentEmbedding);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: i,
|
index: index,
|
||||||
similarity: sim,
|
similarity: sim,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -168,7 +168,7 @@ const createBasicYoutubeSearchAnsweringChain = (llm: BaseChatModel, embeddings:
|
||||||
["user", "{query}"],
|
["user", "{query}"],
|
||||||
]),
|
]),
|
||||||
llm,
|
llm,
|
||||||
strParser,
|
stringParser,
|
||||||
]).withConfig({
|
]).withConfig({
|
||||||
runName: "FinalResponseGenerator",
|
runName: "FinalResponseGenerator",
|
||||||
});
|
});
|
||||||
|
@ -191,9 +191,9 @@ const basicYoutubeSearch = (query: string, history: BaseMessage[], llm: BaseChat
|
||||||
);
|
);
|
||||||
|
|
||||||
handleStream(stream, emitter);
|
handleStream(stream, emitter);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
emitter.emit("error", JSON.stringify({ data: "An error has occurred please try again later" }));
|
||||||
logger.error(`Error in youtube search: ${err}`);
|
logger.error(`Error in youtube search: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { startWebSocketServer } from "./websocket";
|
import { startWebSocketServer } from "./websocket";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import http from "http";
|
import http from "node:http";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import { getPort } from "./config";
|
import { getPort } from "./config";
|
||||||
import logger from "./utils/logger";
|
import logger from "./utils/logger";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import fs from "fs";
|
/* eslint-disable unicorn/prefer-module */
|
||||||
import path from "path";
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import toml from "@iarna/toml";
|
import toml from "@iarna/toml";
|
||||||
|
|
||||||
const configFileName = "config.toml";
|
const configFileName = "config.toml";
|
||||||
|
@ -24,7 +25,7 @@ type RecursivePartial<T> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadConfig = () =>
|
const loadConfig = () =>
|
||||||
toml.parse(fs.readFileSync(path.join(__dirname, `../${configFileName}`), "utf-8")) as unknown as Config;
|
toml.parse(fs.readFileSync(path.join(__dirname, `../${configFileName}`), "utf8")) as unknown as Config;
|
||||||
|
|
||||||
export const getPort = () => loadConfig().GENERAL.PORT;
|
export const getPort = () => loadConfig().GENERAL.PORT;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import Database from "better-sqlite3";
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
|
||||||
const sqlite = new Database("data/db.sqlite");
|
const sqlite = new Database("data/db.sqlite");
|
||||||
const db = drizzle(sqlite, {
|
const database = drizzle(sqlite, {
|
||||||
schema: schema,
|
schema: schema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default db;
|
export default database;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings";
|
import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings";
|
||||||
import { chunkArray } from "@langchain/core/utils/chunk_array";
|
import { chunkArray } from "@langchain/core/utils/chunk_array";
|
||||||
|
|
||||||
export interface HuggingFaceTransformersEmbeddingsParams extends EmbeddingsParams {
|
export interface HuggingFaceTransformersEmbeddingsParameters extends EmbeddingsParams {
|
||||||
modelName: string;
|
modelName: string;
|
||||||
|
|
||||||
model: string;
|
model: string;
|
||||||
|
@ -13,7 +13,10 @@ export interface HuggingFaceTransformersEmbeddingsParams extends EmbeddingsParam
|
||||||
stripNewLines?: boolean;
|
stripNewLines?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HuggingFaceTransformersEmbeddings extends Embeddings implements HuggingFaceTransformersEmbeddingsParams {
|
export class HuggingFaceTransformersEmbeddings
|
||||||
|
extends Embeddings
|
||||||
|
implements HuggingFaceTransformersEmbeddingsParameters
|
||||||
|
{
|
||||||
modelName = "Xenova/all-MiniLM-L6-v2";
|
modelName = "Xenova/all-MiniLM-L6-v2";
|
||||||
|
|
||||||
model = "Xenova/all-MiniLM-L6-v2";
|
model = "Xenova/all-MiniLM-L6-v2";
|
||||||
|
@ -27,7 +30,7 @@ export class HuggingFaceTransformersEmbeddings extends Embeddings implements Hug
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private pipelinePromise: Promise<any>;
|
private pipelinePromise: Promise<any>;
|
||||||
|
|
||||||
constructor(fields?: Partial<HuggingFaceTransformersEmbeddingsParams>) {
|
constructor(fields?: Partial<HuggingFaceTransformersEmbeddingsParameters>) {
|
||||||
super(fields ?? {});
|
super(fields ?? {});
|
||||||
|
|
||||||
this.modelName = fields?.model ?? fields?.modelName ?? this.model;
|
this.modelName = fields?.model ?? fields?.modelName ?? this.model;
|
||||||
|
@ -37,16 +40,15 @@ export class HuggingFaceTransformersEmbeddings extends Embeddings implements Hug
|
||||||
}
|
}
|
||||||
|
|
||||||
async embedDocuments(texts: string[]): Promise<number[][]> {
|
async embedDocuments(texts: string[]): Promise<number[][]> {
|
||||||
const batches = chunkArray(this.stripNewLines ? texts.map(t => t.replace(/\n/g, " ")) : texts, this.batchSize);
|
const batches = chunkArray(this.stripNewLines ? texts.map(t => t.replaceAll("\n", " ")) : texts, this.batchSize);
|
||||||
|
|
||||||
const batchRequests = batches.map(batch => this.runEmbedding(batch));
|
const batchRequests = batches.map(batch => this.runEmbedding(batch));
|
||||||
const batchResponses = await Promise.all(batchRequests);
|
const batchResponses = await Promise.all(batchRequests);
|
||||||
const embeddings: number[][] = [];
|
const embeddings: number[][] = [];
|
||||||
|
|
||||||
for (let i = 0; i < batchResponses.length; i += 1) {
|
for (const batchResponse of batchResponses) {
|
||||||
const batchResponse = batchResponses[i];
|
for (const element of batchResponse) {
|
||||||
for (let j = 0; j < batchResponse.length; j += 1) {
|
embeddings.push(element);
|
||||||
embeddings.push(batchResponse[j]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ export class HuggingFaceTransformersEmbeddings extends Embeddings implements Hug
|
||||||
}
|
}
|
||||||
|
|
||||||
async embedQuery(text: string): Promise<number[]> {
|
async embedQuery(text: string): Promise<number[]> {
|
||||||
const data = await this.runEmbedding([this.stripNewLines ? text.replace(/\n/g, " ") : text]);
|
const data = await this.runEmbedding([this.stripNewLines ? text.replaceAll("\n", " ") : text]);
|
||||||
return data[0];
|
return data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { BaseOutputParser } from "@langchain/core/output_parsers";
|
import { BaseOutputParser } from "@langchain/core/output_parsers";
|
||||||
|
|
||||||
interface LineListOutputParserArgs {
|
interface LineListOutputParserArguments {
|
||||||
key?: string;
|
key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LineListOutputParser extends BaseOutputParser<string[]> {
|
class LineListOutputParser extends BaseOutputParser<string[]> {
|
||||||
private key = "questions";
|
private key = "questions";
|
||||||
|
|
||||||
constructor(args?: LineListOutputParserArgs) {
|
constructor(arguments_?: LineListOutputParserArguments) {
|
||||||
super();
|
super();
|
||||||
this.key = args.key ?? this.key;
|
this.key = arguments_.key ?? this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
static lc_name() {
|
static lc_name() {
|
||||||
|
|
|
@ -36,8 +36,8 @@ export const getAvailableChatModelProviders = async () => {
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading OpenAI models: ${err}`);
|
logger.error(`Error loading OpenAI models: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,8 +85,8 @@ export const getAvailableChatModelProviders = async () => {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading Groq models: ${err}`);
|
logger.error(`Error loading Groq models: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,16 +101,17 @@ export const getAvailableChatModelProviders = async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const { models: ollamaModels } = (await response.json()) as any;
|
const { models: ollamaModels } = (await response.json()) as any;
|
||||||
|
|
||||||
models["ollama"] = ollamaModels.reduce((acc, model) => {
|
// eslint-disable-next-line unicorn/no-array-reduce
|
||||||
acc[model.model] = new ChatOllama({
|
models["ollama"] = ollamaModels.reduce((accumulator, model) => {
|
||||||
|
accumulator[model.model] = new ChatOllama({
|
||||||
baseUrl: ollamaEndpoint,
|
baseUrl: ollamaEndpoint,
|
||||||
model: model.model,
|
model: model.model,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
});
|
});
|
||||||
return acc;
|
return accumulator;
|
||||||
}, {});
|
}, {});
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading Ollama models: ${err}`);
|
logger.error(`Error loading Ollama models: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +138,8 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
||||||
modelName: "text-embedding-3-large",
|
modelName: "text-embedding-3-large",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading OpenAI embeddings: ${err}`);
|
logger.error(`Error loading OpenAI embeddings: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,15 +154,16 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const { models: ollamaModels } = (await response.json()) as any;
|
const { models: ollamaModels } = (await response.json()) as any;
|
||||||
|
|
||||||
models["ollama"] = ollamaModels.reduce((acc, model) => {
|
// eslint-disable-next-line unicorn/no-array-reduce
|
||||||
acc[model.model] = new OllamaEmbeddings({
|
models["ollama"] = ollamaModels.reduce((accumulator, model) => {
|
||||||
|
accumulator[model.model] = new OllamaEmbeddings({
|
||||||
baseUrl: ollamaEndpoint,
|
baseUrl: ollamaEndpoint,
|
||||||
model: model.model,
|
model: model.model,
|
||||||
});
|
});
|
||||||
return acc;
|
return accumulator;
|
||||||
}, {});
|
}, {});
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading Ollama embeddings: ${err}`);
|
logger.error(`Error loading Ollama embeddings: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,8 +179,8 @@ export const getAvailableEmbeddingModelProviders = async () => {
|
||||||
modelName: "Xenova/bert-base-multilingual-uncased",
|
modelName: "Xenova/bert-base-multilingual-uncased",
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
logger.error(`Error loading local embeddings: ${err}`);
|
logger.error(`Error loading local embeddings: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
|
|
|
@ -19,20 +19,20 @@ interface SearxngSearchResult {
|
||||||
iframe_src?: string;
|
iframe_src?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchSearxng = async (query: string, opts?: SearxngSearchOptions) => {
|
export const searchSearxng = async (query: string, options?: SearxngSearchOptions) => {
|
||||||
const searxngURL = getSearxngApiEndpoint();
|
const searxngURL = getSearxngApiEndpoint();
|
||||||
|
|
||||||
const url = new URL(`${searxngURL}/search?format=json`);
|
const url = new URL(`${searxngURL}/search?format=json`);
|
||||||
url.searchParams.append("q", query);
|
url.searchParams.append("q", query);
|
||||||
|
|
||||||
if (opts) {
|
if (options) {
|
||||||
Object.keys(opts).forEach(key => {
|
for (const key of Object.keys(options)) {
|
||||||
if (Array.isArray(opts[key])) {
|
if (Array.isArray(options[key])) {
|
||||||
url.searchParams.append(key, opts[key].join(","));
|
url.searchParams.append(key, options[key].join(","));
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
url.searchParams.append(key, opts[key]);
|
url.searchParams.append(key, options[key]);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await axios.get(url.toString());
|
const res = await axios.get(url.toString());
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
import db from "../db/index";
|
import database from "../db/index";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { chats, messages } from "../db/schema";
|
import { chats, messages } from "../db/schema";
|
||||||
|
|
||||||
|
@ -8,55 +8,55 @@ const router = express.Router();
|
||||||
|
|
||||||
router.get("/", async (_, res) => {
|
router.get("/", async (_, res) => {
|
||||||
try {
|
try {
|
||||||
let chats = await db.query.chats.findMany();
|
let chats = await database.query.chats.findMany();
|
||||||
|
|
||||||
chats = chats.reverse();
|
chats = chats.reverse();
|
||||||
|
|
||||||
return res.status(200).json({ chats: chats });
|
return res.status(200).json({ chats: chats });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in getting chats: ${err.message}`);
|
logger.error(`Error in getting chats: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/:id", async (req, res) => {
|
router.get("/:id", async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const chatExists = await db.query.chats.findFirst({
|
const chatExists = await database.query.chats.findFirst({
|
||||||
where: eq(chats.id, req.params.id),
|
where: eq(chats.id, request.params.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chatExists) {
|
if (!chatExists) {
|
||||||
return res.status(404).json({ message: "Chat not found" });
|
return res.status(404).json({ message: "Chat not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatMessages = await db.query.messages.findMany({
|
const chatMessages = await database.query.messages.findMany({
|
||||||
where: eq(messages.chatId, req.params.id),
|
where: eq(messages.chatId, request.params.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({ chat: chatExists, messages: chatMessages });
|
return res.status(200).json({ chat: chatExists, messages: chatMessages });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in getting chat: ${err.message}`);
|
logger.error(`Error in getting chat: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete(`/:id`, async (req, res) => {
|
router.delete(`/:id`, async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const chatExists = await db.query.chats.findFirst({
|
const chatExists = await database.query.chats.findFirst({
|
||||||
where: eq(chats.id, req.params.id),
|
where: eq(chats.id, request.params.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chatExists) {
|
if (!chatExists) {
|
||||||
return res.status(404).json({ message: "Chat not found" });
|
return res.status(404).json({ message: "Chat not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.delete(chats).where(eq(chats.id, req.params.id)).execute();
|
await database.delete(chats).where(eq(chats.id, request.params.id)).execute();
|
||||||
await db.delete(messages).where(eq(messages.chatId, req.params.id)).execute();
|
await database.delete(messages).where(eq(messages.chatId, request.params.id)).execute();
|
||||||
|
|
||||||
return res.status(200).json({ message: "Chat deleted successfully" });
|
return res.status(200).json({ message: "Chat deleted successfully" });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in deleting chat: ${err.message}`);
|
logger.error(`Error in deleting chat: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ router.get("/", async (_, res) => {
|
||||||
res.status(200).json(config);
|
res.status(200).json(config);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (request, res) => {
|
||||||
const config = req.body;
|
const config = request.body;
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
API_KEYS: {
|
API_KEYS: {
|
||||||
|
|
|
@ -7,16 +7,16 @@ import logger from "../utils/logger";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = req.body;
|
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = request.body;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const chat_history = raw_chat_history.map((msg: any) => {
|
const chat_history = raw_chat_history.map((message: any) => {
|
||||||
if (msg.role === "user") {
|
if (message.role === "user") {
|
||||||
return new HumanMessage(msg.content);
|
return new HumanMessage(message.content);
|
||||||
} else if (msg.role === "assistant") {
|
} else if (message.role === "assistant") {
|
||||||
return new AIMessage(msg.content);
|
return new AIMessage(message.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ router.post("/", async (req, res) => {
|
||||||
const images = await handleImageSearch({ query, chat_history }, llm);
|
const images = await handleImageSearch({ query, chat_history }, llm);
|
||||||
|
|
||||||
res.status(200).json({ images });
|
res.status(200).json({ images });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in image search: ${err.message}`);
|
logger.error(`Error in image search: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getAvailableChatModelProviders, getAvailableEmbeddingModelProviders } f
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", async (req, res) => {
|
router.get("/", async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||||
getAvailableChatModelProviders(),
|
getAvailableChatModelProviders(),
|
||||||
|
@ -12,9 +12,9 @@ router.get("/", async (req, res) => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.status(200).json({ chatModelProviders, embeddingModelProviders });
|
res.status(200).json({ chatModelProviders, embeddingModelProviders });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(err.message);
|
logger.error(error.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,16 @@ import logger from "../utils/logger";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const { chat_history: raw_chat_history, chat_model, chat_model_provider } = req.body;
|
const { chat_history: raw_chat_history, chat_model, chat_model_provider } = request.body;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const chat_history = raw_chat_history.map((msg: any) => {
|
const chat_history = raw_chat_history.map((message: any) => {
|
||||||
if (msg.role === "user") {
|
if (message.role === "user") {
|
||||||
return new HumanMessage(msg.content);
|
return new HumanMessage(message.content);
|
||||||
} else if (msg.role === "assistant") {
|
} else if (message.role === "assistant") {
|
||||||
return new AIMessage(msg.content);
|
return new AIMessage(message.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ router.post("/", async (req, res) => {
|
||||||
const suggestions = await generateSuggestions({ chat_history }, llm);
|
const suggestions = await generateSuggestions({ chat_history }, llm);
|
||||||
|
|
||||||
res.status(200).json({ suggestions: suggestions });
|
res.status(200).json({ suggestions: suggestions });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in generating suggestions: ${err.message}`);
|
logger.error(`Error in generating suggestions: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,16 @@ import handleVideoSearch from "../agents/videoSearchAgent";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (request, res) => {
|
||||||
try {
|
try {
|
||||||
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = req.body;
|
const { query, chat_history: raw_chat_history, chat_model_provider, chat_model } = request.body;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const chat_history = raw_chat_history.map((msg: any) => {
|
const chat_history = raw_chat_history.map((message: any) => {
|
||||||
if (msg.role === "user") {
|
if (message.role === "user") {
|
||||||
return new HumanMessage(msg.content);
|
return new HumanMessage(message.content);
|
||||||
} else if (msg.role === "assistant") {
|
} else if (message.role === "assistant") {
|
||||||
return new AIMessage(msg.content);
|
return new AIMessage(message.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ router.post("/", async (req, res) => {
|
||||||
const videos = await handleVideoSearch({ chat_history, query }, llm);
|
const videos = await handleVideoSearch({ chat_history, query }, llm);
|
||||||
|
|
||||||
res.status(200).json({ videos });
|
res.status(200).json({ videos });
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
res.status(500).json({ message: "An error has occurred." });
|
res.status(500).json({ message: "An error has occurred." });
|
||||||
logger.error(`Error in video search: ${err.message}`);
|
logger.error(`Error in video search: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,26 +3,26 @@ import { handleMessage } from "./messageHandler";
|
||||||
import { getAvailableEmbeddingModelProviders, getAvailableChatModelProviders } from "../lib/providers";
|
import { getAvailableEmbeddingModelProviders, getAvailableChatModelProviders } from "../lib/providers";
|
||||||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import type { IncomingMessage } from "http";
|
import type { IncomingMessage } from "node:http";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
import { ChatOpenAI } from "@langchain/openai";
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
|
|
||||||
export const handleConnection = async (ws: WebSocket, request: IncomingMessage) => {
|
export const handleConnection = async (ws: WebSocket, request: IncomingMessage) => {
|
||||||
try {
|
try {
|
||||||
const searchParams = new URL(request.url, `http://${request.headers.host}`).searchParams;
|
const searchParameters = new URL(request.url, `http://${request.headers.host}`).searchParams;
|
||||||
|
|
||||||
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
const [chatModelProviders, embeddingModelProviders] = await Promise.all([
|
||||||
getAvailableChatModelProviders(),
|
getAvailableChatModelProviders(),
|
||||||
getAvailableEmbeddingModelProviders(),
|
getAvailableEmbeddingModelProviders(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const chatModelProvider = searchParams.get("chatModelProvider") || Object.keys(chatModelProviders)[0];
|
const chatModelProvider = searchParameters.get("chatModelProvider") || Object.keys(chatModelProviders)[0];
|
||||||
const chatModel = searchParams.get("chatModel") || Object.keys(chatModelProviders[chatModelProvider])[0];
|
const chatModel = searchParameters.get("chatModel") || Object.keys(chatModelProviders[chatModelProvider])[0];
|
||||||
|
|
||||||
const embeddingModelProvider =
|
const embeddingModelProvider =
|
||||||
searchParams.get("embeddingModelProvider") || Object.keys(embeddingModelProviders)[0];
|
searchParameters.get("embeddingModelProvider") || Object.keys(embeddingModelProviders)[0];
|
||||||
const embeddingModel =
|
const embeddingModel =
|
||||||
searchParams.get("embeddingModel") || Object.keys(embeddingModelProviders[embeddingModelProvider])[0];
|
searchParameters.get("embeddingModel") || Object.keys(embeddingModelProviders[embeddingModelProvider])[0];
|
||||||
|
|
||||||
let llm: BaseChatModel | undefined;
|
let llm: BaseChatModel | undefined;
|
||||||
let embeddings: Embeddings | undefined;
|
let embeddings: Embeddings | undefined;
|
||||||
|
@ -36,10 +36,10 @@ export const handleConnection = async (ws: WebSocket, request: IncomingMessage)
|
||||||
} else if (chatModelProvider == "custom_openai") {
|
} else if (chatModelProvider == "custom_openai") {
|
||||||
llm = new ChatOpenAI({
|
llm = new ChatOpenAI({
|
||||||
modelName: chatModel,
|
modelName: chatModel,
|
||||||
openAIApiKey: searchParams.get("openAIApiKey"),
|
openAIApiKey: searchParameters.get("openAIApiKey"),
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
configuration: {
|
configuration: {
|
||||||
baseURL: searchParams.get("openAIBaseURL"),
|
baseURL: searchParameters.get("openAIBaseURL"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export const handleConnection = async (ws: WebSocket, request: IncomingMessage)
|
||||||
ws.on("message", async message => await handleMessage(message.toString(), ws, llm, embeddings));
|
ws.on("message", async message => await handleMessage(message.toString(), ws, llm, embeddings));
|
||||||
|
|
||||||
ws.on("close", () => logger.debug("Connection closed"));
|
ws.on("close", () => logger.debug("Connection closed"));
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -74,6 +74,6 @@ export const handleConnection = async (ws: WebSocket, request: IncomingMessage)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
ws.close();
|
ws.close();
|
||||||
logger.error(err);
|
logger.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { initServer } from "./websocketServer";
|
import { initServer } from "./websocketServer";
|
||||||
import http from "http";
|
import http from "node:http";
|
||||||
|
|
||||||
export const startWebSocketServer = (server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => {
|
export const startWebSocketServer = (server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) => {
|
||||||
initServer(server);
|
initServer(server);
|
||||||
|
|
|
@ -9,10 +9,10 @@ import handleRedditSearch from "../agents/redditSearchAgent";
|
||||||
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||||
import type { Embeddings } from "@langchain/core/embeddings";
|
import type { Embeddings } from "@langchain/core/embeddings";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
import db from "../db";
|
import database from "../db";
|
||||||
import { chats, messages } from "../db/schema";
|
import { chats, messages } from "../db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import crypto from "crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
@ -66,7 +66,8 @@ const handleEmitterEvents = (emitter: EventEmitter, ws: WebSocket, messageId: st
|
||||||
emitter.on("end", () => {
|
emitter.on("end", () => {
|
||||||
ws.send(JSON.stringify({ type: "messageEnd", messageId: messageId }));
|
ws.send(JSON.stringify({ type: "messageEnd", messageId: messageId }));
|
||||||
|
|
||||||
db.insert(messages)
|
database
|
||||||
|
.insert(messages)
|
||||||
.values({
|
.values({
|
||||||
content: recievedMessage,
|
content: recievedMessage,
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
|
@ -107,16 +108,14 @@ export const handleMessage = async (message: string, ws: WebSocket, llm: BaseCha
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const history: BaseMessage[] = parsedWSMessage.history.map(msg => {
|
const history: BaseMessage[] = parsedWSMessage.history.map(message_ => {
|
||||||
if (msg[0] === "human") {
|
return message_[0] === "human"
|
||||||
return new HumanMessage({
|
? new HumanMessage({
|
||||||
content: msg[1],
|
content: message_[1],
|
||||||
});
|
})
|
||||||
} else {
|
: new AIMessage({
|
||||||
return new AIMessage({
|
content: message_[1],
|
||||||
content: msg[1],
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parsedWSMessage.type === "message") {
|
if (parsedWSMessage.type === "message") {
|
||||||
|
@ -127,12 +126,12 @@ export const handleMessage = async (message: string, ws: WebSocket, llm: BaseCha
|
||||||
|
|
||||||
handleEmitterEvents(emitter, ws, id, parsedMessage.chatId);
|
handleEmitterEvents(emitter, ws, id, parsedMessage.chatId);
|
||||||
|
|
||||||
const chat = await db.query.chats.findFirst({
|
const chat = await database.query.chats.findFirst({
|
||||||
where: eq(chats.id, parsedMessage.chatId),
|
where: eq(chats.id, parsedMessage.chatId),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
await db
|
await database
|
||||||
.insert(chats)
|
.insert(chats)
|
||||||
.values({
|
.values({
|
||||||
id: parsedMessage.chatId,
|
id: parsedMessage.chatId,
|
||||||
|
@ -143,7 +142,7 @@ export const handleMessage = async (message: string, ws: WebSocket, llm: BaseCha
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await database
|
||||||
.insert(messages)
|
.insert(messages)
|
||||||
.values({
|
.values({
|
||||||
content: parsedMessage.content,
|
content: parsedMessage.content,
|
||||||
|
@ -165,7 +164,7 @@ export const handleMessage = async (message: string, ws: WebSocket, llm: BaseCha
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
@ -173,6 +172,6 @@ export const handleMessage = async (message: string, ws: WebSocket, llm: BaseCha
|
||||||
key: "INVALID_FORMAT",
|
key: "INVALID_FORMAT",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
logger.error(`Failed to handle message: ${err}`);
|
logger.error(`Failed to handle message: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { WebSocketServer } from "ws";
|
import { WebSocketServer } from "ws";
|
||||||
import { handleConnection } from "./connectionManager";
|
import { handleConnection } from "./connectionManager";
|
||||||
import http from "http";
|
import http from "node:http";
|
||||||
import { getPort } from "../config";
|
import { getPort } from "../config";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"skipDefaultLibCheck": true
|
"skipDefaultLibCheck": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
"exclude": ["node_modules", "**/*.spec.ts"]
|
||||||
|
|
|
@ -1,3 +1,23 @@
|
||||||
{
|
{
|
||||||
"overrides": []
|
"extends": ["../.eslintrc.json"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"plugins": ["react", "react-hooks"],
|
||||||
|
"extends": ["plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:react/jsx-runtime"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"postcss.config.js",
|
||||||
|
"tailwind.config.js",
|
||||||
|
"tailwind.config.ts"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"unicorn/prefer-module": "off"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,10 +71,10 @@ const Page = () => {
|
||||||
)}
|
)}
|
||||||
{chats.length > 0 && (
|
{chats.length > 0 && (
|
||||||
<div className="flex flex-col pt-16 lg:pt-24">
|
<div className="flex flex-col pt-16 lg:pt-24">
|
||||||
{chats.map((chat, i) => (
|
{chats.map((chat, index) => (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col space-y-4 border-b border-white-200 dark:border-dark-200 py-6 lg:mx-4"
|
className="flex flex-col space-y-4 border-b border-white-200 dark:border-dark-200 py-6 lg:mx-4"
|
||||||
key={i}
|
key={index}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/c/${chat.id}`}
|
href={`/c/${chat.id}`}
|
||||||
|
|
|
@ -20,13 +20,13 @@ const Chat = ({
|
||||||
rewrite: (messageId: string) => void;
|
rewrite: (messageId: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [dividerWidth, setDividerWidth] = useState(0);
|
const [dividerWidth, setDividerWidth] = useState(0);
|
||||||
const dividerRef = useRef<HTMLDivElement | null>(null);
|
const dividerReference = useRef<HTMLDivElement | null>(null);
|
||||||
const messageEnd = useRef<HTMLDivElement | null>(null);
|
const messageEnd = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateDividerWidth = () => {
|
const updateDividerWidth = () => {
|
||||||
if (dividerRef.current) {
|
if (dividerReference.current) {
|
||||||
setDividerWidth(dividerRef.current.scrollWidth);
|
setDividerWidth(dividerReference.current.scrollWidth);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,29 +43,29 @@ const Chat = ({
|
||||||
messageEnd.current?.scrollIntoView({ behavior: "smooth" });
|
messageEnd.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
|
||||||
if (messages.length === 1) {
|
if (messages.length === 1) {
|
||||||
document.title = `${messages[0].content.substring(0, 30)} - Perplexica`;
|
document.title = `${messages[0].content.slice(0, 30)} - Perplexica`;
|
||||||
}
|
}
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
|
<div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
|
||||||
{messages.map((msg, i) => {
|
{messages.map((message, index) => {
|
||||||
const isLast = i === messages.length - 1;
|
const isLast = index === messages.length - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={msg.messageId}>
|
<Fragment key={message.messageId}>
|
||||||
<MessageBox
|
<MessageBox
|
||||||
key={i}
|
key={index}
|
||||||
message={msg}
|
message={message}
|
||||||
messageIndex={i}
|
messageIndex={index}
|
||||||
history={messages}
|
history={messages}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dividerRef={isLast ? dividerRef : undefined}
|
dividerRef={isLast ? dividerReference : undefined}
|
||||||
isLast={isLast}
|
isLast={isLast}
|
||||||
rewrite={rewrite}
|
rewrite={rewrite}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
/>
|
/>
|
||||||
{!isLast && msg.role === "assistant" && (
|
{!isLast && message.role === "assistant" && (
|
||||||
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Document } from "@langchain/core/documents";
|
||||||
import Navbar from "./Navbar";
|
import Navbar from "./Navbar";
|
||||||
import Chat from "./Chat";
|
import Chat from "./Chat";
|
||||||
import EmptyChat from "./EmptyChat";
|
import EmptyChat from "./EmptyChat";
|
||||||
import crypto from "crypto";
|
import crypto from "node:crypto";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { getSuggestions } from "@/lib/actions";
|
import { getSuggestions } from "@/lib/actions";
|
||||||
|
@ -62,20 +62,20 @@ const useSocket = (url: string, setIsWSReady: (ready: boolean) => void, setError
|
||||||
}
|
}
|
||||||
|
|
||||||
const wsURL = new URL(url);
|
const wsURL = new URL(url);
|
||||||
const searchParams = new URLSearchParams({});
|
const searchParameters = new URLSearchParams({});
|
||||||
|
|
||||||
searchParams.append("chatModel", chatModel!);
|
searchParameters.append("chatModel", chatModel!);
|
||||||
searchParams.append("chatModelProvider", chatModelProvider);
|
searchParameters.append("chatModelProvider", chatModelProvider);
|
||||||
|
|
||||||
if (chatModelProvider === "custom_openai") {
|
if (chatModelProvider === "custom_openai") {
|
||||||
searchParams.append("openAIApiKey", localStorage.getItem("openAIApiKey")!);
|
searchParameters.append("openAIApiKey", localStorage.getItem("openAIApiKey")!);
|
||||||
searchParams.append("openAIBaseURL", localStorage.getItem("openAIBaseURL")!);
|
searchParameters.append("openAIBaseURL", localStorage.getItem("openAIBaseURL")!);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchParams.append("embeddingModel", embeddingModel!);
|
searchParameters.append("embeddingModel", embeddingModel!);
|
||||||
searchParams.append("embeddingModelProvider", embeddingModelProvider);
|
searchParameters.append("embeddingModelProvider", embeddingModelProvider);
|
||||||
|
|
||||||
wsURL.search = searchParams.toString();
|
wsURL.search = searchParameters.toString();
|
||||||
|
|
||||||
const ws = new WebSocket(wsURL.toString());
|
const ws = new WebSocket(wsURL.toString());
|
||||||
|
|
||||||
|
@ -85,26 +85,27 @@ const useSocket = (url: string, setIsWSReady: (ready: boolean) => void, setError
|
||||||
setError(true);
|
setError(true);
|
||||||
toast.error("Failed to connect to the server. Please try again later.");
|
toast.error("Failed to connect to the server. Please try again later.");
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10_000);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.addEventListener("open", () => {
|
||||||
console.log("[DEBUG] open");
|
console.log("[DEBUG] open");
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
setError(false);
|
setError(false);
|
||||||
setIsWSReady(true);
|
setIsWSReady(true);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
setError(true);
|
setError(true);
|
||||||
toast.error("WebSocket connection error.");
|
toast.error("WebSocket connection error.");
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.addEventListener("close", () => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
setError(true);
|
setError(true);
|
||||||
console.log("[DEBUG] closed");
|
console.log("[DEBUG] closed");
|
||||||
};
|
});
|
||||||
|
|
||||||
setWs(ws);
|
setWs(ws);
|
||||||
};
|
};
|
||||||
|
@ -144,17 +145,17 @@ const loadMessages = async (
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const messages = data.messages.map((msg: any) => {
|
const messages = data.messages.map((message: any) => {
|
||||||
return {
|
return {
|
||||||
...msg,
|
...message,
|
||||||
...JSON.parse(msg.metadata),
|
...JSON.parse(message.metadata),
|
||||||
};
|
};
|
||||||
}) as Message[];
|
}) as Message[];
|
||||||
|
|
||||||
setMessages(messages);
|
setMessages(messages);
|
||||||
|
|
||||||
const history = messages.map(msg => {
|
const history = messages.map(message => {
|
||||||
return [msg.role, msg.content];
|
return [message.role, message.content];
|
||||||
}) as [string, string][];
|
}) as [string, string][];
|
||||||
|
|
||||||
console.log("[DEBUG] messages loaded");
|
console.log("[DEBUG] messages loaded");
|
||||||
|
@ -167,8 +168,8 @@ const loadMessages = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatWindow = ({ id }: { id?: string }) => {
|
const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
const searchParams = useSearchParams();
|
const searchParameters = useSearchParams();
|
||||||
const initialMessage = searchParams.get("q");
|
const initialMessage = searchParameters.get("q");
|
||||||
|
|
||||||
const [chatId, setChatId] = useState<string | undefined>(id);
|
const [chatId, setChatId] = useState<string | undefined>(id);
|
||||||
const [newChatCreated, setNewChatCreated] = useState(false);
|
const [newChatCreated, setNewChatCreated] = useState(false);
|
||||||
|
@ -202,10 +203,10 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const messagesRef = useRef<Message[]>([]);
|
const messagesReference = useRef<Message[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesRef.current = messages;
|
messagesReference.current = messages;
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -219,7 +220,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setMessageAppeared(false);
|
setMessageAppeared(false);
|
||||||
|
|
||||||
let sources: Document[] | undefined = undefined;
|
let sources: Document[] | undefined;
|
||||||
let recievedMessage = "";
|
let recievedMessage = "";
|
||||||
let added = false;
|
let added = false;
|
||||||
|
|
||||||
|
@ -237,8 +238,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
setMessages(prevMessages => [
|
setMessages(previousMessages => [
|
||||||
...prevMessages,
|
...previousMessages,
|
||||||
{
|
{
|
||||||
content: message,
|
content: message,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
|
@ -260,8 +261,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
if (data.type === "sources") {
|
if (data.type === "sources") {
|
||||||
sources = data.data;
|
sources = data.data;
|
||||||
if (!added) {
|
if (!added) {
|
||||||
setMessages(prevMessages => [
|
setMessages(previousMessages => [
|
||||||
...prevMessages,
|
...previousMessages,
|
||||||
{
|
{
|
||||||
content: "",
|
content: "",
|
||||||
messageId: data.messageId,
|
messageId: data.messageId,
|
||||||
|
@ -278,8 +279,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
|
|
||||||
if (data.type === "message") {
|
if (data.type === "message") {
|
||||||
if (!added) {
|
if (!added) {
|
||||||
setMessages(prevMessages => [
|
setMessages(previousMessages => [
|
||||||
...prevMessages,
|
...previousMessages,
|
||||||
{
|
{
|
||||||
content: data.data,
|
content: data.data,
|
||||||
messageId: data.messageId,
|
messageId: data.messageId,
|
||||||
|
@ -292,8 +293,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
added = true;
|
added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessages(prev =>
|
setMessages(previous =>
|
||||||
prev.map(message => {
|
previous.map(message => {
|
||||||
if (message.messageId === data.messageId) {
|
if (message.messageId === data.messageId) {
|
||||||
return { ...message, content: message.content + data.data };
|
return { ...message, content: message.content + data.data };
|
||||||
}
|
}
|
||||||
|
@ -307,21 +308,27 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === "messageEnd") {
|
if (data.type === "messageEnd") {
|
||||||
setChatHistory(prevHistory => [...prevHistory, ["human", message], ["assistant", recievedMessage]]);
|
setChatHistory(previousHistory => [...previousHistory, ["human", message], ["assistant", recievedMessage]]);
|
||||||
|
|
||||||
ws?.removeEventListener("message", messageHandler);
|
ws?.removeEventListener("message", messageHandler);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
|
const lastMessage = messagesReference.current.at(-1);
|
||||||
|
|
||||||
if (lastMsg.role === "assistant" && lastMsg.sources && lastMsg.sources.length > 0 && !lastMsg.suggestions) {
|
if (
|
||||||
const suggestions = await getSuggestions(messagesRef.current);
|
lastMessage &&
|
||||||
setMessages(prev =>
|
lastMessage.role === "assistant" &&
|
||||||
prev.map(msg => {
|
lastMessage.sources &&
|
||||||
if (msg.messageId === lastMsg.messageId) {
|
lastMessage.sources.length > 0 &&
|
||||||
return { ...msg, suggestions: suggestions };
|
!lastMessage.suggestions
|
||||||
|
) {
|
||||||
|
const suggestions = await getSuggestions(messagesReference.current);
|
||||||
|
setMessages(previous =>
|
||||||
|
previous.map(message_ => {
|
||||||
|
if (message_.messageId === lastMessage.messageId) {
|
||||||
|
return { ...message_, suggestions: suggestions };
|
||||||
}
|
}
|
||||||
return msg;
|
return message_;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -332,17 +339,19 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const rewrite = (messageId: string) => {
|
const rewrite = (messageId: string) => {
|
||||||
const index = messages.findIndex(msg => msg.messageId === messageId);
|
const index = messages.findIndex(message_ => message_.messageId === messageId);
|
||||||
|
|
||||||
if (index === -1) return;
|
if (index === -1) return;
|
||||||
|
|
||||||
const message = messages[index - 1];
|
const message = messages[index - 1];
|
||||||
|
|
||||||
setMessages(prev => {
|
setMessages(previous => {
|
||||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
// eslint-disable-next-line unicorn/no-useless-spread
|
||||||
|
return [...previous.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||||
});
|
});
|
||||||
setChatHistory(prev => {
|
setChatHistory(previous => {
|
||||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
// eslint-disable-next-line unicorn/no-useless-spread
|
||||||
|
return [...previous.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||||
});
|
});
|
||||||
|
|
||||||
sendMessage(message.content);
|
sendMessage(message.content);
|
||||||
|
|
|
@ -33,8 +33,9 @@ const DeleteChat = ({
|
||||||
const newChats = chats.filter(chat => chat.id !== chatId);
|
const newChats = chats.filter(chat => chat.id !== chatId);
|
||||||
|
|
||||||
setChats(newChats);
|
setChats(newChats);
|
||||||
} catch (err: any) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
toast.error(err.message);
|
} catch (error: any) {
|
||||||
|
toast.error(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setConfirmationDialogOpen(false);
|
setConfirmationDialogOpen(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
|
@ -16,12 +16,12 @@ const EmptyChatMessageInput = ({
|
||||||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
const inputReference = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "/") {
|
if (e.key === "/") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
inputRef.current?.focus();
|
inputReference.current?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ const EmptyChatMessageInput = ({
|
||||||
>
|
>
|
||||||
<div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-5 pt-5 pb-2 rounded-lg w-full border border-light-200 dark:border-dark-200">
|
<div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-5 pt-5 pb-2 rounded-lg w-full border border-light-200 dark:border-dark-200">
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
ref={inputRef}
|
ref={inputReference}
|
||||||
value={message}
|
value={message}
|
||||||
onChange={e => setMessage(e.target.value)}
|
onChange={e => setMessage(e.target.value)}
|
||||||
minRows={2}
|
minRows={2}
|
||||||
|
|
|
@ -8,7 +8,8 @@ const Copy = ({ message, initialMessage }: { message: Message; initialMessage: s
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const contentToCopy = `${initialMessage}${message.sources && message.sources.length > 0 && `\n\nCitations:\n${message.sources?.map((source: any, i: any) => `[${i + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const contentToCopy = `${initialMessage}${message.sources && message.sources.length > 0 && `\n\nCitations:\n${message.sources?.map((source: any, index: any) => `[${index + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
|
||||||
navigator.clipboard.writeText(contentToCopy);
|
navigator.clipboard.writeText(contentToCopy);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 1000);
|
setTimeout(() => setCopied(false), 1000);
|
||||||
|
|
|
@ -36,11 +36,11 @@ const MessageBox = ({
|
||||||
const [speechMessage, setSpeechMessage] = useState(message.content);
|
const [speechMessage, setSpeechMessage] = useState(message.content);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const regex = /\[(\d+)\]/g;
|
const regex = /\[(\d+)]/g;
|
||||||
|
|
||||||
if (message.role === "assistant" && message?.sources && message.sources.length > 0) {
|
if (message.role === "assistant" && message?.sources && message.sources.length > 0) {
|
||||||
return setParsedMessage(
|
return setParsedMessage(
|
||||||
message.content.replace(
|
message.content.replaceAll(
|
||||||
regex,
|
regex,
|
||||||
(_, number) =>
|
(_, number) =>
|
||||||
`<a href="${message.sources?.[number - 1]?.metadata?.url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${number}</a>`,
|
`<a href="${message.sources?.[number - 1]?.metadata?.url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${number}</a>`,
|
||||||
|
@ -48,7 +48,7 @@ const MessageBox = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpeechMessage(message.content.replace(regex, ""));
|
setSpeechMessage(message.content.replaceAll(regex, ""));
|
||||||
setParsedMessage(message.content);
|
setParsedMessage(message.content);
|
||||||
}, [message.content, message.sources, message.role]);
|
}, [message.content, message.sources, message.role]);
|
||||||
|
|
||||||
|
@ -128,8 +128,8 @@ const MessageBox = ({
|
||||||
<h3 className="text-xl font-medium">Related</h3>
|
<h3 className="text-xl font-medium">Related</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-3">
|
<div className="flex flex-col space-y-3">
|
||||||
{message.suggestions.map((suggestion, i) => (
|
{message.suggestions.map((suggestion, index) => (
|
||||||
<div className="flex flex-col space-y-3 text-sm" key={i}>
|
<div className="flex flex-col space-y-3 text-sm" key={index}>
|
||||||
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -19,12 +19,12 @@ const MessageInput = ({ sendMessage, loading }: { sendMessage: (message: string)
|
||||||
}
|
}
|
||||||
}, [textareaRows, mode, message]);
|
}, [textareaRows, mode, message]);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
const inputReference = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "/") {
|
if (e.key === "/") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
inputRef.current?.focus();
|
inputReference.current?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,11 +58,11 @@ const MessageInput = ({ sendMessage, loading }: { sendMessage: (message: string)
|
||||||
>
|
>
|
||||||
{mode === "single" && <Attach />}
|
{mode === "single" && <Attach />}
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
ref={inputRef}
|
ref={inputReference}
|
||||||
value={message}
|
value={message}
|
||||||
onChange={e => setMessage(e.target.value)}
|
onChange={e => setMessage(e.target.value)}
|
||||||
onHeightChange={(height, props) => {
|
onHeightChange={(height, properties) => {
|
||||||
setTextareaRows(Math.ceil(height / props.rowHeight));
|
setTextareaRows(Math.ceil(height / properties.rowHeight));
|
||||||
}}
|
}}
|
||||||
className="transition bg-transparent dark:placeholder:text-white/50 placeholder:text-sm text-sm dark:text-white resize-none focus:outline-none w-full px-2 max-h-24 lg:max-h-36 xl:max-h-48 flex-grow flex-shrink"
|
className="transition bg-transparent dark:placeholder:text-white/50 placeholder:text-sm text-sm dark:text-white resize-none focus:outline-none w-full px-2 max-h-24 lg:max-h-36 xl:max-h-48 flex-grow flex-shrink"
|
||||||
placeholder="Ask a follow-up"
|
placeholder="Ask a follow-up"
|
||||||
|
|
|
@ -54,14 +54,14 @@ const Focus = ({ focusMode, setFocusMode }: { focusMode: string; setFocusMode: (
|
||||||
type="button"
|
type="button"
|
||||||
className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
|
className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{focusMode !== "webSearch" ? (
|
{focusMode === "webSearch" ? (
|
||||||
|
<ScanEye />
|
||||||
|
) : (
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{focusModes.find(mode => mode.key === focusMode)?.icon}
|
{focusModes.find(mode => mode.key === focusMode)?.icon}
|
||||||
<p className="text-xs font-medium">{focusModes.find(mode => mode.key === focusMode)?.title}</p>
|
<p className="text-xs font-medium">{focusModes.find(mode => mode.key === focusMode)?.title}</p>
|
||||||
<ChevronDown size={20} />
|
<ChevronDown size={20} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<ScanEye />
|
|
||||||
)}
|
)}
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Transition
|
<Transition
|
||||||
|
@ -75,10 +75,10 @@ const Focus = ({ focusMode, setFocusMode }: { focusMode: string; setFocusMode: (
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute z-10 w-full">
|
<Popover.Panel className="absolute z-10 w-full">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-1 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-1 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto">
|
||||||
{focusModes.map((mode, i) => (
|
{focusModes.map((mode, index) => (
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
onClick={() => setFocusMode(mode.key)}
|
onClick={() => setFocusMode(mode.key)}
|
||||||
key={i}
|
key={index}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition",
|
"p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition",
|
||||||
focusMode === mode.key
|
focusMode === mode.key
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable @next/next/no-img-element */
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Document } from "@langchain/core/documents";
|
import { Document } from "@langchain/core/documents";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
|
@ -18,12 +17,13 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
||||||
{sources.slice(0, 3).map((source, i) => (
|
{sources.slice(0, 3).map((source, index) => (
|
||||||
<a
|
<a
|
||||||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
key={i}
|
key={index}
|
||||||
href={source.metadata.url}
|
href={source.metadata.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.title}
|
{source.metadata.title}
|
||||||
|
@ -38,12 +38,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
||||||
className="rounded-lg h-4 w-4"
|
className="rounded-lg h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, "")}
|
{source.metadata.url.replaceAll(/.+\/\/|www.|\..+/g, "")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||||
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||||
<span>{i + 1}</span>
|
<span>{index + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -54,14 +54,14 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
||||||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{sources.slice(3, 6).map((source, i) => (
|
{sources.slice(3, 6).map((source, index) => (
|
||||||
<img
|
<img
|
||||||
src={`https://s2.googleusercontent.com/s2/favicons?domain_url=${source.metadata.url}`}
|
src={`https://s2.googleusercontent.com/s2/favicons?domain_url=${source.metadata.url}`}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
alt="favicon"
|
alt="favicon"
|
||||||
className="rounded-lg h-4 w-4"
|
className="rounded-lg h-4 w-4"
|
||||||
key={i}
|
key={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,12 +84,13 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
||||||
<Dialog.Panel 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">
|
<Dialog.Panel 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">
|
||||||
<Dialog.Title className="text-lg font-medium leading-6 dark:text-white">Sources</Dialog.Title>
|
<Dialog.Title className="text-lg font-medium leading-6 dark:text-white">Sources</Dialog.Title>
|
||||||
<div className="grid grid-cols-2 gap-2 overflow-auto max-h-[300px] mt-2 pr-2">
|
<div className="grid grid-cols-2 gap-2 overflow-auto max-h-[300px] mt-2 pr-2">
|
||||||
{sources.map((source, i) => (
|
{sources.map((source, index) => (
|
||||||
<a
|
<a
|
||||||
className="bg-light-secondary hover:bg-light-200 dark:bg-dark-secondary dark:hover:bg-dark-200 border border-light-200 dark:border-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
className="bg-light-secondary hover:bg-light-200 dark:bg-dark-secondary dark:hover:bg-dark-200 border border-light-200 dark:border-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||||
key={i}
|
key={index}
|
||||||
href={source.metadata.url}
|
href={source.metadata.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.title}
|
{source.metadata.title}
|
||||||
|
@ -104,12 +105,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
||||||
className="rounded-lg h-4 w-4"
|
className="rounded-lg h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||||
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, "")}
|
{source.metadata.url.replaceAll(/.+\/\/|www.|\..+/g, "")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||||
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||||
<span>{i + 1}</span>
|
<span>{index + 1}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -10,7 +10,7 @@ const Navbar = ({ messages }: { messages: Message[] }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length > 0) {
|
if (messages.length > 0) {
|
||||||
const newTitle =
|
const newTitle =
|
||||||
messages[0].content.length > 20 ? `${messages[0].content.substring(0, 20).trim()}...` : messages[0].content;
|
messages[0].content.length > 20 ? `${messages[0].content.slice(0, 20).trim()}...` : messages[0].content;
|
||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
const newTimeAgo = formatTimeDifference(new Date(), messages[0].createdAt);
|
const newTimeAgo = formatTimeDifference(new Date(), messages[0].createdAt);
|
||||||
setTimeAgo(newTimeAgo);
|
setTimeAgo(newTimeAgo);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable @next/next/no-img-element */
|
|
||||||
import { ImagesIcon, PlusIcon } from "lucide-react";
|
import { ImagesIcon, PlusIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Lightbox from "yet-another-react-lightbox";
|
import Lightbox from "yet-another-react-lightbox";
|
||||||
|
@ -15,6 +14,7 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
const [images, setImages] = useState<Image[] | null>(null);
|
const [images, setImages] = useState<Image[] | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [slides, setSlides] = useState<any[]>([]);
|
const [slides, setSlides] = useState<any[]>([]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -64,9 +64,9 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
)}
|
)}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{[...Array(4)].map((_, i) => (
|
{Array.from({ length: 4 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={index}
|
||||||
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -76,25 +76,25 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{images.length > 4
|
{images.length > 4
|
||||||
? images.slice(0, 3).map((image, i) => (
|
? images.slice(0, 3).map((image, index) => (
|
||||||
<img
|
<img
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||||
}}
|
}}
|
||||||
key={i}
|
key={index}
|
||||||
src={image.img_src}
|
src={image.img_src}
|
||||||
alt={image.title}
|
alt={image.title}
|
||||||
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: images.map((image, i) => (
|
: images.map((image, index) => (
|
||||||
<img
|
<img
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||||
}}
|
}}
|
||||||
key={i}
|
key={index}
|
||||||
src={image.img_src}
|
src={image.img_src}
|
||||||
alt={image.title}
|
alt={image.title}
|
||||||
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
||||||
|
@ -106,9 +106,9 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{images.slice(3, 6).map((image, i) => (
|
{images.slice(3, 6).map((image, index) => (
|
||||||
<img
|
<img
|
||||||
key={i}
|
key={index}
|
||||||
src={image.img_src}
|
src={image.img_src}
|
||||||
alt={image.title}
|
alt={image.title}
|
||||||
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable @next/next/no-img-element */
|
|
||||||
import { PlayCircle, PlayIcon, PlusIcon, VideoIcon } from "lucide-react";
|
import { PlayCircle, PlayIcon, PlusIcon, VideoIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Lightbox, { GenericSlide, VideoSlide } from "yet-another-react-lightbox";
|
import Lightbox, { GenericSlide, VideoSlide } from "yet-another-react-lightbox";
|
||||||
|
@ -79,9 +78,9 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
)}
|
)}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{[...Array(4)].map((_, i) => (
|
{Array.from({ length: 4 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={index}
|
||||||
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -91,14 +90,14 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{videos.length > 4
|
{videos.length > 4
|
||||||
? videos.slice(0, 3).map((video, i) => (
|
? videos.slice(0, 3).map((video, index) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||||
}}
|
}}
|
||||||
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
||||||
key={i}
|
key={index}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={video.img_src}
|
src={video.img_src}
|
||||||
|
@ -111,14 +110,14 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
: videos.map((video, i) => (
|
: videos.map((video, index) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||||
}}
|
}}
|
||||||
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
||||||
key={i}
|
key={index}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={video.img_src}
|
src={video.img_src}
|
||||||
|
@ -137,9 +136,9 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
||||||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center space-x-1">
|
<div className="flex flex-row items-center space-x-1">
|
||||||
{videos.slice(3, 6).map((video, i) => (
|
{videos.slice(3, 6).map((video, index) => (
|
||||||
<img
|
<img
|
||||||
key={i}
|
key={index}
|
||||||
src={video.img_src}
|
src={video.img_src}
|
||||||
alt={video.title}
|
alt={video.title}
|
||||||
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
|
/* eslint-disable unicorn/no-nested-ternary */
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { CloudUpload, RefreshCcw, RefreshCw } from "lucide-react";
|
import { CloudUpload, RefreshCcw, RefreshCw } from "lucide-react";
|
||||||
import React, { Fragment, useEffect, useState, type SelectHTMLAttributes } from "react";
|
import React, { Fragment, useEffect, useState, type SelectHTMLAttributes } from "react";
|
||||||
import ThemeSwitcher from "./theme/Switcher";
|
import ThemeSwitcher from "./theme/Switcher";
|
||||||
|
|
||||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
interface InputProperties extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
const Input = ({ className, ...restProps }: InputProps) => {
|
const Input = ({ className, ...restProperties }: InputProperties) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
{...restProps}
|
{...restProperties}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
||||||
className,
|
className,
|
||||||
|
@ -18,14 +19,14 @@ const Input = ({ className, ...restProps }: InputProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
interface SelectProperties extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
options: { value: string; label: string; disabled?: boolean }[];
|
options: { value: string; label: string; disabled?: boolean }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Select = ({ className, options, ...restProps }: SelectProps) => {
|
export const Select = ({ className, options, ...restProperties }: SelectProperties) => {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
{...restProps}
|
{...restProperties}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
||||||
className,
|
className,
|
||||||
|
@ -129,8 +130,8 @@ const SettingsDialog = ({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: (is
|
||||||
localStorage.setItem("embeddingModel", selectedEmbeddingModel!);
|
localStorage.setItem("embeddingModel", selectedEmbeddingModel!);
|
||||||
localStorage.setItem("openAIApiKey", customOpenAIApiKey!);
|
localStorage.setItem("openAIApiKey", customOpenAIApiKey!);
|
||||||
localStorage.setItem("openAIBaseURL", customOpenAIBaseURL!);
|
localStorage.setItem("openAIBaseURL", customOpenAIBaseURL!);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.log(err);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
|
|
@ -46,9 +46,9 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
||||||
<SquarePen className="cursor-pointer" />
|
<SquarePen className="cursor-pointer" />
|
||||||
</a>
|
</a>
|
||||||
<VerticalIconContainer>
|
<VerticalIconContainer>
|
||||||
{navLinks.map((link, i) => (
|
{navLinks.map((link, index) => (
|
||||||
<Link
|
<Link
|
||||||
key={i}
|
key={index}
|
||||||
href={link.href}
|
href={link.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex flex-row items-center justify-center cursor-pointer hover:bg-black/10 dark:hover:bg-white/10 duration-150 transition w-full py-2 rounded-lg",
|
"relative flex flex-row items-center justify-center cursor-pointer hover:bg-black/10 dark:hover:bg-white/10 duration-150 transition w-full py-2 rounded-lg",
|
||||||
|
@ -70,10 +70,10 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="fixed bottom-0 w-full z-50 flex flex-row items-center gap-x-6 bg-light-primary dark:bg-dark-primary px-4 py-4 shadow-sm lg:hidden">
|
<div className="fixed bottom-0 w-full z-50 flex flex-row items-center gap-x-6 bg-light-primary dark:bg-dark-primary px-4 py-4 shadow-sm lg:hidden">
|
||||||
{navLinks.map((link, i) => (
|
{navLinks.map((link, index) => (
|
||||||
<Link
|
<Link
|
||||||
href={link.href}
|
href={link.href}
|
||||||
key={i}
|
key={index}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex flex-col items-center space-y-1 text-center w-full",
|
"relative flex flex-col items-center space-y-1 text-center w-full",
|
||||||
link.active ? "text-black dark:text-white" : "text-black dark:text-white/70",
|
link.active ? "text-black dark:text-white" : "text-black dark:text-white/70",
|
||||||
|
|
|
@ -9,12 +9,13 @@ export const formatTimeDifference = (date1: Date | string, date2: Date | string)
|
||||||
|
|
||||||
const diffInSeconds = Math.floor(Math.abs(date2.getTime() - date1.getTime()) / 1000);
|
const diffInSeconds = Math.floor(Math.abs(date2.getTime() - date1.getTime()) / 1000);
|
||||||
|
|
||||||
if (diffInSeconds < 60) return `${diffInSeconds} second${diffInSeconds !== 1 ? "s" : ""}`;
|
if (diffInSeconds < 60) return `${diffInSeconds} second${diffInSeconds === 1 ? "" : "s"}`;
|
||||||
else if (diffInSeconds < 3600)
|
else if (diffInSeconds < 3600)
|
||||||
return `${Math.floor(diffInSeconds / 60)} minute${Math.floor(diffInSeconds / 60) !== 1 ? "s" : ""}`;
|
return `${Math.floor(diffInSeconds / 60)} minute${Math.floor(diffInSeconds / 60) === 1 ? "" : "s"}`;
|
||||||
else if (diffInSeconds < 86400)
|
else if (diffInSeconds < 86_400)
|
||||||
return `${Math.floor(diffInSeconds / 3600)} hour${Math.floor(diffInSeconds / 3600) !== 1 ? "s" : ""}`;
|
return `${Math.floor(diffInSeconds / 3600)} hour${Math.floor(diffInSeconds / 3600) === 1 ? "" : "s"}`;
|
||||||
else if (diffInSeconds < 31536000)
|
else if (diffInSeconds < 31_536_000)
|
||||||
return `${Math.floor(diffInSeconds / 86400)} day${Math.floor(diffInSeconds / 86400) !== 1 ? "s" : ""}`;
|
return `${Math.floor(diffInSeconds / 86_400)} day${Math.floor(diffInSeconds / 86_400) === 1 ? "" : "s"}`;
|
||||||
else return `${Math.floor(diffInSeconds / 31536000)} year${Math.floor(diffInSeconds / 31536000) !== 1 ? "s" : ""}`;
|
else
|
||||||
|
return `${Math.floor(diffInSeconds / 31_536_000)} year${Math.floor(diffInSeconds / 31_536_000) === 1 ? "" : "s"}`;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue