From cddc7939150f7d577bccb6a75f03e6743da61bb8 Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:52:14 +0530 Subject: [PATCH 01/28] feat(videoSearch): use XML parsing, use few shot prompting --- src/lib/chains/videoSearchAgent.ts | 72 +++++++++++++++++++----------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/lib/chains/videoSearchAgent.ts b/src/lib/chains/videoSearchAgent.ts index f7cb156..8e158f5 100644 --- a/src/lib/chains/videoSearchAgent.ts +++ b/src/lib/chains/videoSearchAgent.ts @@ -3,33 +3,19 @@ import { RunnableMap, RunnableLambda, } from '@langchain/core/runnables'; -import { PromptTemplate } from '@langchain/core/prompts'; +import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts'; import formatChatHistoryAsString from '../utils/formatHistory'; import { BaseMessage } from '@langchain/core/messages'; import { StringOutputParser } from '@langchain/core/output_parsers'; import { searchSearxng } from '../searxng'; import type { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import LineOutputParser from '../outputParsers/lineOutputParser'; -const VideoSearchChainPrompt = ` - You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search Youtube for videos. - You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation. - - Example: - 1. Follow up question: How does a car work? - Rephrased: How does a car work? - - 2. Follow up question: What is the theory of relativity? - Rephrased: What is theory of relativity - - 3. Follow up question: How does an AC work? - Rephrased: How does an AC work - - Conversation: - {chat_history} - - Follow up question: {query} - Rephrased question: - `; +const videoSearchChainPrompt = ` +You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search Youtube for videos. +You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation. +Output only the rephrased query wrapped in an XML element. Do not include any explanation or additional text. +`; type VideoSearchChainInput = { chat_history: BaseMessage[]; @@ -55,12 +41,46 @@ const createVideoSearchChain = (llm: BaseChatModel) => { return input.query; }, }), - PromptTemplate.fromTemplate(VideoSearchChainPrompt), + ChatPromptTemplate.fromMessages([ + ['system', videoSearchChainPrompt], + [ + 'user', + '\n\n\nHow does a car work?\n' + ], + [ + 'assistant', + 'How does a car work?' + ], + [ + 'user', + '\n\n\nWhat is the theory of relativity?\n' + ], + [ + 'assistant', + 'Theory of relativity' + ], + [ + 'user', + '\n\n\nHow does an AC work?\n' + ], + [ + 'assistant', + 'AC working' + ], + [ + 'user', + '{chat_history}\n\n{query}\n' + ] + ]), llm, strParser, RunnableLambda.from(async (input: string) => { - input = input.replace(/.*?<\/think>/g, ''); - + const queryParser = new LineOutputParser({ + key: 'query' + }); + return (await queryParser.parse(input)); + }), + RunnableLambda.from(async (input: string) => { const res = await searchSearxng(input, { engines: ['youtube'], }); @@ -92,8 +112,8 @@ const handleVideoSearch = ( input: VideoSearchChainInput, llm: BaseChatModel, ) => { - const VideoSearchChain = createVideoSearchChain(llm); - return VideoSearchChain.invoke(input); + const videoSearchChain = createVideoSearchChain(llm); + return videoSearchChain.invoke(input); }; export default handleVideoSearch; From b48b0eeb0e5cfea3a8de9f7d2f1052119f8af9ec Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:52:30 +0530 Subject: [PATCH 02/28] feat(imageSearch): use XML parsing, implement few shot prompting --- src/lib/chains/imageSearchAgent.ts | 60 ++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/lib/chains/imageSearchAgent.ts b/src/lib/chains/imageSearchAgent.ts index 4fd684f..993cba9 100644 --- a/src/lib/chains/imageSearchAgent.ts +++ b/src/lib/chains/imageSearchAgent.ts @@ -3,32 +3,18 @@ import { RunnableMap, RunnableLambda, } from '@langchain/core/runnables'; -import { PromptTemplate } from '@langchain/core/prompts'; +import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts'; import formatChatHistoryAsString from '../utils/formatHistory'; import { BaseMessage } from '@langchain/core/messages'; import { StringOutputParser } from '@langchain/core/output_parsers'; import { searchSearxng } from '../searxng'; import type { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import LineOutputParser from '../outputParsers/lineOutputParser'; const imageSearchChainPrompt = ` You will be given a conversation below and a follow up question. You need to rephrase the follow-up question so it is a standalone question that can be used by the LLM to search the web for images. You need to make sure the rephrased question agrees with the conversation and is relevant to the conversation. - -Example: -1. Follow up question: What is a cat? -Rephrased: A cat - -2. Follow up question: What is a car? How does it works? -Rephrased: Car working - -3. Follow up question: How does an AC work? -Rephrased: AC working - -Conversation: -{chat_history} - -Follow up question: {query} -Rephrased question: +Output only the rephrased query wrapped in an XML element. Do not include any explanation or additional text. `; type ImageSearchChainInput = { @@ -54,12 +40,48 @@ const createImageSearchChain = (llm: BaseChatModel) => { return input.query; }, }), - PromptTemplate.fromTemplate(imageSearchChainPrompt), + ChatPromptTemplate.fromMessages([ + ['system', imageSearchChainPrompt], + [ + "user", + "\n\n\nWhat is a cat?\n" + ], + [ + "assistant", + "A cat" + ], + + [ + "user", + "\n\n\nWhat is a car? How does it work?\n" + ], + [ + "assistant", + "Car working" + ], + [ + "user", + "\n\n\nHow does an AC work?\n" + ], + [ + "assistant", + "AC working" + ], + [ + 'user', + '{chat_history}\n\n{query}\n' + ] + ]), llm, strParser, RunnableLambda.from(async (input: string) => { - input = input.replace(/.*?<\/think>/g, ''); + const queryParser = new LineOutputParser({ + key: 'query' + }) + return (await queryParser.parse(input)) + }), + RunnableLambda.from(async (input: string) => { const res = await searchSearxng(input, { engines: ['bing images', 'google images'], }); From 7c4aa683a244137626c7d820c322f50c87fb79f7 Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:57:32 +0530 Subject: [PATCH 03/28] feat(chains): remove unused imports --- src/lib/chains/imageSearchAgent.ts | 43 ++++++++++++------------------ src/lib/chains/videoSearchAgent.ts | 31 ++++++++------------- 2 files changed, 28 insertions(+), 46 deletions(-) diff --git a/src/lib/chains/imageSearchAgent.ts b/src/lib/chains/imageSearchAgent.ts index 993cba9..a91b7bb 100644 --- a/src/lib/chains/imageSearchAgent.ts +++ b/src/lib/chains/imageSearchAgent.ts @@ -3,7 +3,7 @@ import { RunnableMap, RunnableLambda, } from '@langchain/core/runnables'; -import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts'; +import { ChatPromptTemplate } from '@langchain/core/prompts'; import formatChatHistoryAsString from '../utils/formatHistory'; import { BaseMessage } from '@langchain/core/messages'; import { StringOutputParser } from '@langchain/core/output_parsers'; @@ -43,43 +43,34 @@ const createImageSearchChain = (llm: BaseChatModel) => { ChatPromptTemplate.fromMessages([ ['system', imageSearchChainPrompt], [ - "user", - "\n\n\nWhat is a cat?\n" - ], - [ - "assistant", - "A cat" + 'user', + '\n\n\nWhat is a cat?\n', ], + ['assistant', 'A cat'], [ - "user", - "\n\n\nWhat is a car? How does it work?\n" - ], - [ - "assistant", - "Car working" - ], - [ - "user", - "\n\n\nHow does an AC work?\n" - ], - [ - "assistant", - "AC working" + 'user', + '\n\n\nWhat is a car? How does it work?\n', ], + ['assistant', 'Car working'], [ 'user', - '{chat_history}\n\n{query}\n' - ] + '\n\n\nHow does an AC work?\n', + ], + ['assistant', 'AC working'], + [ + 'user', + '{chat_history}\n\n{query}\n', + ], ]), llm, strParser, RunnableLambda.from(async (input: string) => { const queryParser = new LineOutputParser({ - key: 'query' - }) + key: 'query', + }); - return (await queryParser.parse(input)) + return await queryParser.parse(input); }), RunnableLambda.from(async (input: string) => { const res = await searchSearxng(input, { diff --git a/src/lib/chains/videoSearchAgent.ts b/src/lib/chains/videoSearchAgent.ts index 8e158f5..3f878a8 100644 --- a/src/lib/chains/videoSearchAgent.ts +++ b/src/lib/chains/videoSearchAgent.ts @@ -3,7 +3,7 @@ import { RunnableMap, RunnableLambda, } from '@langchain/core/runnables'; -import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts'; +import { ChatPromptTemplate } from '@langchain/core/prompts'; import formatChatHistoryAsString from '../utils/formatHistory'; import { BaseMessage } from '@langchain/core/messages'; import { StringOutputParser } from '@langchain/core/output_parsers'; @@ -45,40 +45,31 @@ const createVideoSearchChain = (llm: BaseChatModel) => { ['system', videoSearchChainPrompt], [ 'user', - '\n\n\nHow does a car work?\n' - ], - [ - 'assistant', - 'How does a car work?' + '\n\n\nHow does a car work?\n', ], + ['assistant', 'How does a car work?'], [ 'user', - '\n\n\nWhat is the theory of relativity?\n' - ], - [ - 'assistant', - 'Theory of relativity' + '\n\n\nWhat is the theory of relativity?\n', ], + ['assistant', 'Theory of relativity'], [ 'user', - '\n\n\nHow does an AC work?\n' - ], - [ - 'assistant', - 'AC working' + '\n\n\nHow does an AC work?\n', ], + ['assistant', 'AC working'], [ 'user', - '{chat_history}\n\n{query}\n' - ] + '{chat_history}\n\n{query}\n', + ], ]), llm, strParser, RunnableLambda.from(async (input: string) => { const queryParser = new LineOutputParser({ - key: 'query' + key: 'query', }); - return (await queryParser.parse(input)); + return await queryParser.parse(input); }), RunnableLambda.from(async (input: string) => { const res = await searchSearxng(input, { From 7f629073850b7c80a528001c7cf2db6e3d5fe5d3 Mon Sep 17 00:00:00 2001 From: Willie Zutz Date: Sat, 19 Jul 2025 08:53:11 -0600 Subject: [PATCH 04/28] feat(weather): update measurement units to Imperial/Metric --- src/app/api/weather/route.ts | 15 +++++++++++---- src/app/settings/page.tsx | 28 ++++++++++++++++------------ src/components/WeatherWidget.tsx | 6 ++++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/app/api/weather/route.ts b/src/app/api/weather/route.ts index 1f9867f..afaf8a6 100644 --- a/src/app/api/weather/route.ts +++ b/src/app/api/weather/route.ts @@ -1,7 +1,10 @@ export const POST = async (req: Request) => { try { - const body: { lat: number; lng: number; temperatureUnit: 'C' | 'F' } = - await req.json(); + const body: { + lat: number; + lng: number; + measureUnit: 'Imperial' | 'Metric'; + } = await req.json(); if (!body.lat || !body.lng) { return Response.json( @@ -13,7 +16,9 @@ export const POST = async (req: Request) => { } const res = await fetch( - `https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}¤t=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${body.temperatureUnit === 'C' ? '' : '&temperature_unit=fahrenheit'}`, + `https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}¤t=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${ + body.measureUnit === 'Metric' ? '' : '&temperature_unit=fahrenheit' + }${body.measureUnit === 'Metric' ? '' : '&wind_speed_unit=mph'}`, ); const data = await res.json(); @@ -35,13 +40,15 @@ export const POST = async (req: Request) => { windSpeed: number; icon: string; temperatureUnit: 'C' | 'F'; + windSpeedUnit: 'm/s' | 'mph'; } = { temperature: data.current.temperature_2m, condition: '', humidity: data.current.relative_humidity_2m, windSpeed: data.current.wind_speed_10m, icon: '', - temperatureUnit: body.temperatureUnit, + temperatureUnit: body.measureUnit === 'Metric' ? 'C' : 'F', + windSpeedUnit: body.measureUnit === 'Metric' ? 'm/s' : 'mph', }; const code = data.current.weather_code; diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 045226c..1b13c9c 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -148,7 +148,9 @@ const Page = () => { const [automaticImageSearch, setAutomaticImageSearch] = useState(false); const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false); const [systemInstructions, setSystemInstructions] = useState(''); - const [temperatureUnit, setTemperatureUnit] = useState<'C' | 'F'>('C'); + const [measureUnit, setMeasureUnit] = useState<'Imperial' | 'Metric'>( + 'Metric', + ); const [savingStates, setSavingStates] = useState>({}); useEffect(() => { @@ -211,7 +213,9 @@ const Page = () => { setSystemInstructions(localStorage.getItem('systemInstructions')!); - setTemperatureUnit(localStorage.getItem('temperatureUnit')! as 'C' | 'F'); + setMeasureUnit( + localStorage.getItem('measureUnit')! as 'Imperial' | 'Metric', + ); setIsLoading(false); }; @@ -371,8 +375,8 @@ const Page = () => { localStorage.setItem('embeddingModel', value); } else if (key === 'systemInstructions') { localStorage.setItem('systemInstructions', value); - } else if (key === 'temperatureUnit') { - localStorage.setItem('temperatureUnit', value.toString()); + } else if (key === 'measureUnit') { + localStorage.setItem('measureUnit', value.toString()); } } catch (err) { console.error('Failed to save:', err); @@ -430,22 +434,22 @@ const Page = () => {

- Temperature Unit + Measurement Units

{ + setConfig((prev) => ({ + ...prev!, + ollamaApiKey: e.target.value, + })); + }} + onSave={(value) => saveConfig('ollamaApiKey', value)} + /> +
+

GROQ API Key diff --git a/src/lib/config.ts b/src/lib/config.ts index d885e13..79d69dc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -31,6 +31,7 @@ interface Config { }; OLLAMA: { API_URL: string; + API_KEY: string; }; DEEPSEEK: { API_KEY: string; @@ -86,6 +87,8 @@ export const getSearxngApiEndpoint = () => export const getOllamaApiEndpoint = () => loadConfig().MODELS.OLLAMA.API_URL; +export const getOllamaApiKey = () => loadConfig().MODELS.OLLAMA.API_KEY; + export const getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY; export const getAimlApiKey = () => loadConfig().MODELS.AIMLAPI.API_KEY; diff --git a/src/lib/providers/ollama.ts b/src/lib/providers/ollama.ts index d5c7899..cb0b848 100644 --- a/src/lib/providers/ollama.ts +++ b/src/lib/providers/ollama.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { getKeepAlive, getOllamaApiEndpoint } from '../config'; +import { getKeepAlive, getOllamaApiEndpoint, getOllamaApiKey } from '../config'; import { ChatModel, EmbeddingModel } from '.'; export const PROVIDER_INFO = { @@ -11,6 +11,7 @@ import { OllamaEmbeddings } from '@langchain/ollama'; export const loadOllamaChatModels = async () => { const ollamaApiEndpoint = getOllamaApiEndpoint(); + const ollamaApiKey = getOllamaApiKey(); if (!ollamaApiEndpoint) return {}; @@ -33,6 +34,9 @@ export const loadOllamaChatModels = async () => { model: model.model, temperature: 0.7, keepAlive: getKeepAlive(), + ...(ollamaApiKey + ? { headers: { Authorization: `Bearer ${ollamaApiKey}` } } + : {}), }), }; }); @@ -46,6 +50,7 @@ export const loadOllamaChatModels = async () => { export const loadOllamaEmbeddingModels = async () => { const ollamaApiEndpoint = getOllamaApiEndpoint(); + const ollamaApiKey = getOllamaApiKey(); if (!ollamaApiEndpoint) return {}; @@ -66,6 +71,9 @@ export const loadOllamaEmbeddingModels = async () => { model: new OllamaEmbeddings({ baseUrl: ollamaApiEndpoint, model: model.model, + ...(ollamaApiKey + ? { headers: { Authorization: `Bearer ${ollamaApiKey}` } } + : {}), }), }; }); From 72f26b4370bd2b6206cbc5db37273920203aea8c Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:47:49 +0530 Subject: [PATCH 27/28] feat(upload): save files uploaded after chat created --- src/app/api/chat/route.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 2d53b75..8798075 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -1,11 +1,7 @@ -import prompts from '@/lib/prompts'; -import MetaSearchAgent from '@/lib/search/metaSearchAgent'; import crypto from 'crypto'; import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages'; import { EventEmitter } from 'stream'; import { - chatModelProviders, - embeddingModelProviders, getAvailableChatModelProviders, getAvailableEmbeddingModelProviders, } from '@/lib/providers'; @@ -138,6 +134,8 @@ const handleHistorySave = async ( where: eq(chats.id, message.chatId), }); + const fileData = files.map(getFileDetails); + if (!chat) { await db .insert(chats) @@ -146,9 +144,16 @@ const handleHistorySave = async ( title: message.content, createdAt: new Date().toString(), focusMode: focusMode, - files: files.map(getFileDetails), + files: fileData, }) .execute(); + } else if (JSON.stringify(chat.files ?? []) != JSON.stringify(fileData)) { + db + .update(chats) + .set({ + files: files.map(getFileDetails), + }) + .where(eq(chats.id, message.chatId)); } const messageExists = await db.query.messages.findFirst({ From 8dc54efbdd3cac2da3092e2864dfd52bd5dc92d1 Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:48:55 +0530 Subject: [PATCH 28/28] feat(chat-route): lint & beautify --- src/app/api/chat/route.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 8798075..ba88da6 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -148,8 +148,7 @@ const handleHistorySave = async ( }) .execute(); } else if (JSON.stringify(chat.files ?? []) != JSON.stringify(fileData)) { - db - .update(chats) + db.update(chats) .set({ files: files.map(getFileDetails), })