mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-11-21 12:38:14 +00:00
Compare commits
12 Commits
1a8889c71c
...
3d5d04eda0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d5d04eda0 | ||
|
|
07a17925b1 | ||
|
|
3bcf646af1 | ||
|
|
e499c0b96e | ||
|
|
33b736e1e8 | ||
|
|
5e1746f646 | ||
|
|
41fe009847 | ||
|
|
70c1f7230c | ||
|
|
c0771095a6 | ||
|
|
0856896aff | ||
|
|
2e736613c5 | ||
|
|
046daf442a |
Binary file not shown.
|
Before Width: | Height: | Size: 16 MiB |
@@ -1,4 +1,4 @@
|
|||||||
import handleImageSearch from '@/lib/agents/media/image';
|
import searchImages from '@/lib/agents/media/image';
|
||||||
import ModelRegistry from '@/lib/models/registry';
|
import ModelRegistry from '@/lib/models/registry';
|
||||||
import { ModelWithProvider } from '@/lib/models/types';
|
import { ModelWithProvider } from '@/lib/models/types';
|
||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
|
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
|
||||||
@@ -13,6 +13,13 @@ export const POST = async (req: Request) => {
|
|||||||
try {
|
try {
|
||||||
const body: ImageSearchBody = await req.json();
|
const body: ImageSearchBody = await req.json();
|
||||||
|
|
||||||
|
const registry = new ModelRegistry();
|
||||||
|
|
||||||
|
const llm = await registry.loadChatModel(
|
||||||
|
body.chatModel.providerId,
|
||||||
|
body.chatModel.key,
|
||||||
|
);
|
||||||
|
|
||||||
const chatHistory = body.chatHistory
|
const chatHistory = body.chatHistory
|
||||||
.map((msg: any) => {
|
.map((msg: any) => {
|
||||||
if (msg.role === 'user') {
|
if (msg.role === 'user') {
|
||||||
@@ -23,16 +30,9 @@ export const POST = async (req: Request) => {
|
|||||||
})
|
})
|
||||||
.filter((msg) => msg !== undefined) as BaseMessage[];
|
.filter((msg) => msg !== undefined) as BaseMessage[];
|
||||||
|
|
||||||
const registry = new ModelRegistry();
|
const images = await searchImages(
|
||||||
|
|
||||||
const llm = await registry.loadChatModel(
|
|
||||||
body.chatModel.providerId,
|
|
||||||
body.chatModel.key,
|
|
||||||
);
|
|
||||||
|
|
||||||
const images = await handleImageSearch(
|
|
||||||
{
|
{
|
||||||
chat_history: chatHistory,
|
chatHistory: chatHistory,
|
||||||
query: body.query,
|
query: body.query,
|
||||||
},
|
},
|
||||||
llm,
|
llm,
|
||||||
|
|||||||
@@ -30,12 +30,6 @@ export const POST = async (req: Request) => {
|
|||||||
body.optimizationMode = body.optimizationMode || 'balanced';
|
body.optimizationMode = body.optimizationMode || 'balanced';
|
||||||
body.stream = body.stream || false;
|
body.stream = body.stream || false;
|
||||||
|
|
||||||
const history: BaseMessage[] = body.history.map((msg) => {
|
|
||||||
return msg[0] === 'human'
|
|
||||||
? new HumanMessage({ content: msg[1] })
|
|
||||||
: new AIMessage({ content: msg[1] });
|
|
||||||
});
|
|
||||||
|
|
||||||
const registry = new ModelRegistry();
|
const registry = new ModelRegistry();
|
||||||
|
|
||||||
const [llm, embeddings] = await Promise.all([
|
const [llm, embeddings] = await Promise.all([
|
||||||
@@ -46,6 +40,12 @@ export const POST = async (req: Request) => {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const history: BaseMessage[] = body.history.map((msg) => {
|
||||||
|
return msg[0] === 'human'
|
||||||
|
? new HumanMessage({ content: msg[1] })
|
||||||
|
: new AIMessage({ content: msg[1] });
|
||||||
|
});
|
||||||
|
|
||||||
const searchHandler: MetaSearchAgentType = searchHandlers[body.focusMode];
|
const searchHandler: MetaSearchAgentType = searchHandlers[body.focusMode];
|
||||||
|
|
||||||
if (!searchHandler) {
|
if (!searchHandler) {
|
||||||
@@ -128,7 +128,7 @@ export const POST = async (req: Request) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
controller.close();
|
controller.close();
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
});
|
});
|
||||||
|
|
||||||
emitter.on('data', (data: string) => {
|
emitter.on('data', (data: string) => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import generateSuggestions from '@/lib/agents/suggestions';
|
import generateSuggestions from '@/lib/agents/suggestions';
|
||||||
import ModelRegistry from '@/lib/models/registry';
|
import ModelRegistry from '@/lib/models/registry';
|
||||||
import { ModelWithProvider } from '@/lib/models/types';
|
import { ModelWithProvider } from '@/lib/models/types';
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
|
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
|
||||||
|
|
||||||
interface SuggestionsGenerationBody {
|
interface SuggestionsGenerationBody {
|
||||||
@@ -13,6 +12,13 @@ export const POST = async (req: Request) => {
|
|||||||
try {
|
try {
|
||||||
const body: SuggestionsGenerationBody = await req.json();
|
const body: SuggestionsGenerationBody = await req.json();
|
||||||
|
|
||||||
|
const registry = new ModelRegistry();
|
||||||
|
|
||||||
|
const llm = await registry.loadChatModel(
|
||||||
|
body.chatModel.providerId,
|
||||||
|
body.chatModel.key,
|
||||||
|
);
|
||||||
|
|
||||||
const chatHistory = body.chatHistory
|
const chatHistory = body.chatHistory
|
||||||
.map((msg: any) => {
|
.map((msg: any) => {
|
||||||
if (msg.role === 'user') {
|
if (msg.role === 'user') {
|
||||||
@@ -23,16 +29,9 @@ export const POST = async (req: Request) => {
|
|||||||
})
|
})
|
||||||
.filter((msg) => msg !== undefined) as BaseMessage[];
|
.filter((msg) => msg !== undefined) as BaseMessage[];
|
||||||
|
|
||||||
const registry = new ModelRegistry();
|
|
||||||
|
|
||||||
const llm = await registry.loadChatModel(
|
|
||||||
body.chatModel.providerId,
|
|
||||||
body.chatModel.key,
|
|
||||||
);
|
|
||||||
|
|
||||||
const suggestions = await generateSuggestions(
|
const suggestions = await generateSuggestions(
|
||||||
{
|
{
|
||||||
chat_history: chatHistory,
|
chatHistory,
|
||||||
},
|
},
|
||||||
llm,
|
llm,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ export const POST = async (req: Request) => {
|
|||||||
try {
|
try {
|
||||||
const body: VideoSearchBody = await req.json();
|
const body: VideoSearchBody = await req.json();
|
||||||
|
|
||||||
|
const registry = new ModelRegistry();
|
||||||
|
|
||||||
|
const llm = await registry.loadChatModel(
|
||||||
|
body.chatModel.providerId,
|
||||||
|
body.chatModel.key,
|
||||||
|
);
|
||||||
|
|
||||||
const chatHistory = body.chatHistory
|
const chatHistory = body.chatHistory
|
||||||
.map((msg: any) => {
|
.map((msg: any) => {
|
||||||
if (msg.role === 'user') {
|
if (msg.role === 'user') {
|
||||||
@@ -23,16 +30,9 @@ export const POST = async (req: Request) => {
|
|||||||
})
|
})
|
||||||
.filter((msg) => msg !== undefined) as BaseMessage[];
|
.filter((msg) => msg !== undefined) as BaseMessage[];
|
||||||
|
|
||||||
const registry = new ModelRegistry();
|
|
||||||
|
|
||||||
const llm = await registry.loadChatModel(
|
|
||||||
body.chatModel.providerId,
|
|
||||||
body.chatModel.key,
|
|
||||||
);
|
|
||||||
|
|
||||||
const videos = await handleVideoSearch(
|
const videos = await handleVideoSearch(
|
||||||
{
|
{
|
||||||
chat_history: chatHistory,
|
chatHistory: chatHistory,
|
||||||
query: body.query,
|
query: body.query,
|
||||||
},
|
},
|
||||||
llm,
|
llm,
|
||||||
|
|||||||
@@ -205,11 +205,11 @@ const MessageBox = ({
|
|||||||
<div className="lg:sticky lg:top-20 flex flex-col items-center space-y-3 w-full lg:w-3/12 z-30 h-full pb-4">
|
<div className="lg:sticky lg:top-20 flex flex-col items-center space-y-3 w-full lg:w-3/12 z-30 h-full pb-4">
|
||||||
<SearchImages
|
<SearchImages
|
||||||
query={section.userMessage.content}
|
query={section.userMessage.content}
|
||||||
chatHistory={chatTurns.slice(0, sectionIndex * 2)}
|
chatHistory={chatTurns}
|
||||||
messageId={section.assistantMessage.messageId}
|
messageId={section.assistantMessage.messageId}
|
||||||
/>
|
/>
|
||||||
<SearchVideos
|
<SearchVideos
|
||||||
chatHistory={chatTurns.slice(0, sectionIndex * 2)}
|
chatHistory={chatTurns}
|
||||||
query={section.userMessage.content}
|
query={section.userMessage.content}
|
||||||
messageId={section.assistantMessage.messageId}
|
messageId={section.assistantMessage.messageId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const AddModel = ({
|
|||||||
>
|
>
|
||||||
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
||||||
<div className="px-6 pt-6 pb-4">
|
<div className="px-6 pt-6 pb-4">
|
||||||
<h3 className="text-black/90 dark:text-white/90 font-medium">
|
<h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
|
||||||
Add new {type === 'chat' ? 'chat' : 'embedding'} model
|
Add new {type === 'chat' ? 'chat' : 'embedding'} model
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +115,7 @@ const AddModel = ({
|
|||||||
<input
|
<input
|
||||||
value={modelName}
|
value={modelName}
|
||||||
onChange={(e) => setModelName(e.target.value)}
|
onChange={(e) => setModelName(e.target.value)}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder="e.g., GPT-4"
|
placeholder="e.g., GPT-4"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
@@ -128,7 +128,7 @@ const AddModel = ({
|
|||||||
<input
|
<input
|
||||||
value={modelKey}
|
value={modelKey}
|
||||||
onChange={(e) => setModelKey(e.target.value)}
|
onChange={(e) => setModelKey(e.target.value)}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder="e.g., gpt-4"
|
placeholder="e.g., gpt-4"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
@@ -140,7 +140,7 @@ const AddModel = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="animate-spin" size={16} />
|
<Loader2 className="animate-spin" size={16} />
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const AddProvider = ({
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="px-3 md:px-4 py-1.5 md:py-2 rounded-lg text-xs sm:text-sm border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
|
className="px-3 md:px-4 py-1.5 md:py-2 rounded-lg text-xs sm:text-xs border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5 md:w-4 md:h-4" />
|
<Plus className="w-3.5 h-3.5 md:w-4 md:h-4" />
|
||||||
<span>Add Connection</span>
|
<span>Add Connection</span>
|
||||||
@@ -119,7 +119,7 @@ const AddProvider = ({
|
|||||||
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1">
|
||||||
<div className="px-6 pt-6 pb-4">
|
<div className="px-6 pt-6 pb-4">
|
||||||
<h3 className="text-black/90 dark:text-white/90 font-medium">
|
<h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
|
||||||
Add new connection
|
Add new connection
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,7 +178,7 @@ const AddProvider = ({
|
|||||||
[field.key]: event.target.value,
|
[field.key]: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder={
|
placeholder={
|
||||||
(field as StringUIConfigField).placeholder
|
(field as StringUIConfigField).placeholder
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ const AddProvider = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="animate-spin" size={16} />
|
<Loader2 className="animate-spin" size={16} />
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ const ModelProvider = ({
|
|||||||
<Plug2 size={14} className="text-sky-500" />
|
<Plug2 size={14} className="text-sky-500" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm lg:text-base text-black dark:text-white font-medium">
|
<p className="text-sm lg:text-sm text-black dark:text-white font-medium">
|
||||||
{modelProvider.name}
|
{modelProvider.name}
|
||||||
</p>
|
</p>
|
||||||
{modelCount > 0 && (
|
{modelCount > 0 && (
|
||||||
<p className="text-[10px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[10px] lg:text-[11px] text-black/50 dark:text-white/50">
|
||||||
{modelCount} model{modelCount !== 1 ? 's' : ''} configured
|
{modelCount} model{modelCount !== 1 ? 's' : ''} configured
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -109,7 +109,7 @@ const ModelProvider = ({
|
|||||||
<div className="flex flex-col gap-y-4 px-5 py-4">
|
<div className="flex flex-col gap-y-4 px-5 py-4">
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-2">
|
||||||
<div className="flex flex-row w-full justify-between items-center">
|
<div className="flex flex-row w-full justify-between items-center">
|
||||||
<p className="text-[11px] lg:text-xs font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
|
<p className="text-[11px] lg:text-[11px] font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
|
||||||
Chat Models
|
Chat Models
|
||||||
</p>
|
</p>
|
||||||
{!modelProvider.chatModels.some((m) => m.key === 'error') && (
|
{!modelProvider.chatModels.some((m) => m.key === 'error') && (
|
||||||
@@ -122,7 +122,7 @@ const ModelProvider = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{modelProvider.chatModels.some((m) => m.key === 'error') ? (
|
{modelProvider.chatModels.some((m) => m.key === 'error') ? (
|
||||||
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
<div className="flex flex-row items-center gap-2 text-xs lg:text-xs text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
||||||
<AlertCircle size={16} className="shrink-0" />
|
<AlertCircle size={16} className="shrink-0" />
|
||||||
<span className="break-words">
|
<span className="break-words">
|
||||||
{
|
{
|
||||||
@@ -144,7 +144,7 @@ const ModelProvider = ({
|
|||||||
{modelProvider.chatModels.map((model, index) => (
|
{modelProvider.chatModels.map((model, index) => (
|
||||||
<div
|
<div
|
||||||
key={`${modelProvider.id}-chat-${model.key}-${index}`}
|
key={`${modelProvider.id}-chat-${model.key}-${index}`}
|
||||||
className="flex flex-row items-center space-x-1.5 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
|
className="flex flex-row items-center space-x-1.5 text-xs lg:text-xs text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
|
||||||
>
|
>
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
<button
|
<button
|
||||||
@@ -164,7 +164,7 @@ const ModelProvider = ({
|
|||||||
|
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-2">
|
||||||
<div className="flex flex-row w-full justify-between items-center">
|
<div className="flex flex-row w-full justify-between items-center">
|
||||||
<p className="text-[11px] lg:text-xs font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
|
<p className="text-[11px] lg:text-[11px] font-medium text-black/70 dark:text-white/70 uppercase tracking-wide">
|
||||||
Embedding Models
|
Embedding Models
|
||||||
</p>
|
</p>
|
||||||
{!modelProvider.embeddingModels.some((m) => m.key === 'error') && (
|
{!modelProvider.embeddingModels.some((m) => m.key === 'error') && (
|
||||||
@@ -177,7 +177,7 @@ const ModelProvider = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{modelProvider.embeddingModels.some((m) => m.key === 'error') ? (
|
{modelProvider.embeddingModels.some((m) => m.key === 'error') ? (
|
||||||
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
<div className="flex flex-row items-center gap-2 text-xs lg:text-xs text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
||||||
<AlertCircle size={16} className="shrink-0" />
|
<AlertCircle size={16} className="shrink-0" />
|
||||||
<span className="break-words">
|
<span className="break-words">
|
||||||
{
|
{
|
||||||
@@ -199,7 +199,7 @@ const ModelProvider = ({
|
|||||||
{modelProvider.embeddingModels.map((model, index) => (
|
{modelProvider.embeddingModels.map((model, index) => (
|
||||||
<div
|
<div
|
||||||
key={`${modelProvider.id}-embedding-${model.key}-${index}`}
|
key={`${modelProvider.id}-embedding-${model.key}-${index}`}
|
||||||
className="flex flex-row items-center space-x-1.5 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
|
className="flex flex-row items-center space-x-1.5 text-xs lg:text-xs text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5 border border-light-200 dark:border-dark-200"
|
||||||
>
|
>
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const ModelSelect = ({
|
|||||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
<div className="space-y-3 lg:space-y-5">
|
<div className="space-y-3 lg:space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
<h4 className="text-sm lg:text-sm text-black dark:text-white">
|
||||||
Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'}
|
Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
@@ -86,7 +86,7 @@ const ModelSelect = ({
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="!text-xs lg:!text-sm"
|
className="!text-xs lg:!text-[13px]"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const Models = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 space-y-6 overflow-y-auto py-6">
|
<div className="flex-1 space-y-6 overflow-y-auto py-6">
|
||||||
<div className="flex flex-col px-6 gap-y-4">
|
<div className="flex flex-col px-6 gap-y-4">
|
||||||
<h3 className="text-xs lg:text-sm text-black/70 dark:text-white/70">
|
<h3 className="text-xs lg:text-xs text-black/70 dark:text-white/70">
|
||||||
Select models
|
Select models
|
||||||
</h3>
|
</h3>
|
||||||
<ModelSelect
|
<ModelSelect
|
||||||
@@ -38,7 +38,7 @@ const Models = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="border-t border-light-200 dark:border-dark-200" />
|
<div className="border-t border-light-200 dark:border-dark-200" />
|
||||||
<div className="flex flex-row justify-between items-center px-6 ">
|
<div className="flex flex-row justify-between items-center px-6 ">
|
||||||
<p className="text-xs lg:text-sm text-black/70 dark:text-white/70">
|
<p className="text-xs lg:text-xs text-black/70 dark:text-white/70">
|
||||||
Manage connections
|
Manage connections
|
||||||
</p>
|
</p>
|
||||||
<AddProvider modelProviders={fields} setProviders={setProviders} />
|
<AddProvider modelProviders={fields} setProviders={setProviders} />
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const UpdateProvider = ({
|
|||||||
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1">
|
||||||
<div className="px-6 pt-6 pb-4">
|
<div className="px-6 pt-6 pb-4">
|
||||||
<h3 className="text-black/90 dark:text-white/90 font-medium">
|
<h3 className="text-black/90 dark:text-white/90 font-medium text-sm">
|
||||||
Update connection
|
Update connection
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +150,7 @@ const UpdateProvider = ({
|
|||||||
[field.key]: event.target.value,
|
[field.key]: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder={
|
placeholder={
|
||||||
(field as StringUIConfigField).placeholder
|
(field as StringUIConfigField).placeholder
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ const UpdateProvider = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
className="px-4 py-2 rounded-lg text-[13px] bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="animate-spin" size={16} />
|
<Loader2 className="animate-spin" size={16} />
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ const SettingsDialogue = ({
|
|||||||
<div className="flex flex-1 flex-col overflow-hidden">
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
<div className="border-b border-light-200/60 px-6 pb-6 lg:pt-6 dark:border-dark-200/60 flex-shrink-0">
|
<div className="border-b border-light-200/60 px-6 pb-6 lg:pt-6 dark:border-dark-200/60 flex-shrink-0">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h4 className="font-medium text-black dark:text-white text-sm lg:text-base">
|
<h4 className="font-medium text-black dark:text-white text-sm lg:text-sm">
|
||||||
{selectedSection.name}
|
{selectedSection.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const SettingsSelect = ({
|
|||||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
<div className="space-y-3 lg:space-y-5">
|
<div className="space-y-3 lg:space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
<h4 className="text-sm lg:text-sm text-black dark:text-white">
|
||||||
{field.name}
|
{field.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
@@ -135,7 +135,7 @@ const SettingsInput = ({
|
|||||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
<div className="space-y-3 lg:space-y-5">
|
<div className="space-y-3 lg:space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
<h4 className="text-sm lg:text-sm text-black dark:text-white">
|
||||||
{field.name}
|
{field.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
@@ -147,7 +147,7 @@ const SettingsInput = ({
|
|||||||
value={value ?? field.default ?? ''}
|
value={value ?? field.default ?? ''}
|
||||||
onChange={(event) => setValue(event.target.value)}
|
onChange={(event) => setValue(event.target.value)}
|
||||||
onBlur={(event) => handleSave(event.target.value)}
|
onBlur={(event) => handleSave(event.target.value)}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
type="text"
|
type="text"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -211,7 +211,7 @@ const SettingsTextarea = ({
|
|||||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
<div className="space-y-3 lg:space-y-5">
|
<div className="space-y-3 lg:space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
<h4 className="text-sm lg:text-sm text-black dark:text-white">
|
||||||
{field.name}
|
{field.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
@@ -223,7 +223,7 @@ const SettingsTextarea = ({
|
|||||||
value={value ?? field.default ?? ''}
|
value={value ?? field.default ?? ''}
|
||||||
onChange={(event) => setValue(event.target.value)}
|
onChange={(event) => setValue(event.target.value)}
|
||||||
onBlur={(event) => handleSave(event.target.value)}
|
onBlur={(event) => handleSave(event.target.value)}
|
||||||
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-3 py-2 lg:px-4 lg:py-3 pr-10 !text-xs lg:!text-[13px] text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -289,7 +289,7 @@ const SettingsSwitch = ({
|
|||||||
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
<section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
|
||||||
<div className="flex flex-row items-center space-x-3 lg:space-x-5 w-full justify-between">
|
<div className="flex flex-row items-center space-x-3 lg:space-x-5 w-full justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm lg:text-base text-black dark:text-white">
|
<h4 className="text-sm lg:text-sm text-black dark:text-white">
|
||||||
{field.name}
|
{field.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
<p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
|
||||||
|
|||||||
@@ -7,101 +7,59 @@ import {
|
|||||||
} from '@langchain/core/runnables';
|
} from '@langchain/core/runnables';
|
||||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||||
import { BaseMessage } from '@langchain/core/messages';
|
import { BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages';
|
||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
import { StringOutputParser } from '@langchain/core/output_parsers';
|
||||||
import { searchSearxng } from '@/lib/searxng';
|
import { searchSearxng } from '@/lib/searxng';
|
||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import LineOutputParser from '@/lib/outputParsers/lineOutputParser';
|
import LineOutputParser from '@/lib/outputParsers/lineOutputParser';
|
||||||
|
import { imageSearchFewShots, imageSearchPrompt } from '@/lib/prompts/media/image';
|
||||||
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.
|
|
||||||
Output only the rephrased query wrapped in an XML <query> element. Do not include any explanation or additional text.
|
|
||||||
`;
|
|
||||||
|
|
||||||
type ImageSearchChainInput = {
|
type ImageSearchChainInput = {
|
||||||
chat_history: BaseMessage[];
|
chatHistory: BaseMessage[];
|
||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ImageSearchResult {
|
type ImageSearchResult = {
|
||||||
img_src: string;
|
img_src: string;
|
||||||
url: string;
|
url: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const outputParser = new LineOutputParser({
|
||||||
|
key: 'query',
|
||||||
|
})
|
||||||
|
|
||||||
const createImageSearchChain = (llm: BaseChatModel) => {
|
const searchImages = async (
|
||||||
return RunnableSequence.from([
|
|
||||||
RunnableMap.from({
|
|
||||||
chat_history: (input: ImageSearchChainInput) => {
|
|
||||||
return formatChatHistoryAsString(input.chat_history);
|
|
||||||
},
|
|
||||||
query: (input: ImageSearchChainInput) => {
|
|
||||||
return input.query;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ChatPromptTemplate.fromMessages([
|
|
||||||
['system', imageSearchChainPrompt],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nWhat is a cat?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>A cat</query>'],
|
|
||||||
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nWhat is a car? How does it work?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>Car working</query>'],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>AC working</query>'],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>{chat_history}</conversation>\n<follow_up>\n{query}\n</follow_up>',
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
llm,
|
|
||||||
strParser,
|
|
||||||
RunnableLambda.from(async (input: string) => {
|
|
||||||
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'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const images: ImageSearchResult[] = [];
|
|
||||||
|
|
||||||
res.results.forEach((result) => {
|
|
||||||
if (result.img_src && result.url && result.title) {
|
|
||||||
images.push({
|
|
||||||
img_src: result.img_src,
|
|
||||||
url: result.url,
|
|
||||||
title: result.title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return images.slice(0, 10);
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageSearch = (
|
|
||||||
input: ImageSearchChainInput,
|
input: ImageSearchChainInput,
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
) => {
|
) => {
|
||||||
const imageSearchChain = createImageSearchChain(llm);
|
const chatPrompt = await ChatPromptTemplate.fromMessages([
|
||||||
return imageSearchChain.invoke(input);
|
new SystemMessage(imageSearchPrompt),
|
||||||
|
...imageSearchFewShots,
|
||||||
|
new HumanMessage(`<conversation>\n${formatChatHistoryAsString(input.chatHistory)}\n</conversation>\n<follow_up>\n${input.query}\n</follow_up>`)
|
||||||
|
]).formatMessages({})
|
||||||
|
|
||||||
|
const res = await llm.invoke(chatPrompt)
|
||||||
|
|
||||||
|
const query = await outputParser.invoke(res)
|
||||||
|
|
||||||
|
const searchRes = await searchSearxng(query!, {
|
||||||
|
engines: ['bing images', 'google images'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const images: ImageSearchResult[] = [];
|
||||||
|
|
||||||
|
searchRes.results.forEach((result) => {
|
||||||
|
if (result.img_src && result.url && result.title) {
|
||||||
|
images.push({
|
||||||
|
img_src: result.img_src,
|
||||||
|
url: result.url,
|
||||||
|
title: result.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return images.slice(0, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleImageSearch;
|
export default searchImages;
|
||||||
@@ -1,110 +1,65 @@
|
|||||||
import {
|
|
||||||
RunnableSequence,
|
|
||||||
RunnableMap,
|
|
||||||
RunnableLambda,
|
|
||||||
} from '@langchain/core/runnables';
|
|
||||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||||
import { BaseMessage } from '@langchain/core/messages';
|
import { BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages';
|
||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
|
|
||||||
import { searchSearxng } from '@/lib/searxng';
|
import { searchSearxng } from '@/lib/searxng';
|
||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import LineOutputParser from '@/lib/outputParsers/lineOutputParser';
|
import LineOutputParser from '@/lib/outputParsers/lineOutputParser';
|
||||||
|
import { videoSearchFewShots, videoSearchPrompt } from '@/lib/prompts/media/videos';
|
||||||
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 <query> element. Do not include any explanation or additional text.
|
|
||||||
`;
|
|
||||||
|
|
||||||
type VideoSearchChainInput = {
|
type VideoSearchChainInput = {
|
||||||
chat_history: BaseMessage[];
|
chatHistory: BaseMessage[];
|
||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface VideoSearchResult {
|
type VideoSearchResult = {
|
||||||
img_src: string;
|
img_src: string;
|
||||||
url: string;
|
url: string;
|
||||||
title: string;
|
title: string;
|
||||||
iframe_src: string;
|
iframe_src: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const strParser = new StringOutputParser();
|
const outputParser = new LineOutputParser({
|
||||||
|
key: 'query',
|
||||||
|
});
|
||||||
|
|
||||||
const createVideoSearchChain = (llm: BaseChatModel) => {
|
const searchVideos = async (
|
||||||
return RunnableSequence.from([
|
|
||||||
RunnableMap.from({
|
|
||||||
chat_history: (input: VideoSearchChainInput) => {
|
|
||||||
return formatChatHistoryAsString(input.chat_history);
|
|
||||||
},
|
|
||||||
query: (input: VideoSearchChainInput) => {
|
|
||||||
return input.query;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ChatPromptTemplate.fromMessages([
|
|
||||||
['system', videoSearchChainPrompt],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nHow does a car work?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>How does a car work?</query>'],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nWhat is the theory of relativity?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>Theory of relativity</query>'],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
|
||||||
],
|
|
||||||
['assistant', '<query>AC working</query>'],
|
|
||||||
[
|
|
||||||
'user',
|
|
||||||
'<conversation>{chat_history}</conversation>\n<follow_up>\n{query}\n</follow_up>',
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
llm,
|
|
||||||
strParser,
|
|
||||||
RunnableLambda.from(async (input: string) => {
|
|
||||||
const queryParser = new LineOutputParser({
|
|
||||||
key: 'query',
|
|
||||||
});
|
|
||||||
return await queryParser.parse(input);
|
|
||||||
}),
|
|
||||||
RunnableLambda.from(async (input: string) => {
|
|
||||||
const res = await searchSearxng(input, {
|
|
||||||
engines: ['youtube'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const videos: VideoSearchResult[] = [];
|
|
||||||
|
|
||||||
res.results.forEach((result) => {
|
|
||||||
if (
|
|
||||||
result.thumbnail &&
|
|
||||||
result.url &&
|
|
||||||
result.title &&
|
|
||||||
result.iframe_src
|
|
||||||
) {
|
|
||||||
videos.push({
|
|
||||||
img_src: result.thumbnail,
|
|
||||||
url: result.url,
|
|
||||||
title: result.title,
|
|
||||||
iframe_src: result.iframe_src,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return videos.slice(0, 10);
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleVideoSearch = (
|
|
||||||
input: VideoSearchChainInput,
|
input: VideoSearchChainInput,
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
) => {
|
) => {
|
||||||
const videoSearchChain = createVideoSearchChain(llm);
|
const chatPrompt = await ChatPromptTemplate.fromMessages([
|
||||||
return videoSearchChain.invoke(input);
|
new SystemMessage(videoSearchPrompt),
|
||||||
|
...videoSearchFewShots,
|
||||||
|
new HumanMessage(`<conversation>${formatChatHistoryAsString(input.chatHistory)}\n</conversation>\n<follow_up>\n${input.query}\n</follow_up>`)
|
||||||
|
]).formatMessages({})
|
||||||
|
|
||||||
|
const res = await llm.invoke(chatPrompt)
|
||||||
|
|
||||||
|
const query = await outputParser.invoke(res)
|
||||||
|
|
||||||
|
const searchRes = await searchSearxng(query!, {
|
||||||
|
engines: ['youtube'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const videos: VideoSearchResult[] = [];
|
||||||
|
|
||||||
|
searchRes.results.forEach((result) => {
|
||||||
|
if (
|
||||||
|
result.thumbnail &&
|
||||||
|
result.url &&
|
||||||
|
result.title &&
|
||||||
|
result.iframe_src
|
||||||
|
) {
|
||||||
|
videos.push({
|
||||||
|
img_src: result.thumbnail,
|
||||||
|
url: result.url,
|
||||||
|
title: result.title,
|
||||||
|
iframe_src: result.iframe_src,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return videos.slice(0, 10);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleVideoSearch;
|
export default searchVideos;
|
||||||
|
|||||||
@@ -1,55 +1,32 @@
|
|||||||
import { RunnableSequence, RunnableMap } from '@langchain/core/runnables';
|
|
||||||
import ListLineOutputParser from '@/lib/outputParsers/listLineOutputParser';
|
import ListLineOutputParser from '@/lib/outputParsers/listLineOutputParser';
|
||||||
import { PromptTemplate } from '@langchain/core/prompts';
|
import { ChatPromptTemplate, PromptTemplate } from '@langchain/core/prompts';
|
||||||
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
import formatChatHistoryAsString from '@/lib/utils/formatHistory';
|
||||||
import { BaseMessage } from '@langchain/core/messages';
|
import { BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages';
|
||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||||
import { ChatOpenAI } from '@langchain/openai';
|
import { suggestionGeneratorPrompt } from '@/lib/prompts/suggestions';
|
||||||
|
|
||||||
const suggestionGeneratorPrompt = `
|
|
||||||
You are an AI suggestion generator for an AI powered search engine. You will be given a conversation below. You need to generate 4-5 suggestions based on the conversation. The suggestion should be relevant to the conversation that can be used by the user to ask the chat model for more information.
|
|
||||||
You need to make sure the suggestions are relevant to the conversation and are helpful to the user. Keep a note that the user might use these suggestions to ask a chat model for more information.
|
|
||||||
Make sure the suggestions are medium in length and are informative and relevant to the conversation.
|
|
||||||
|
|
||||||
Provide these suggestions separated by newlines between the XML tags <suggestions> and </suggestions>. For example:
|
|
||||||
|
|
||||||
<suggestions>
|
|
||||||
Tell me more about SpaceX and their recent projects
|
|
||||||
What is the latest news on SpaceX?
|
|
||||||
Who is the CEO of SpaceX?
|
|
||||||
</suggestions>
|
|
||||||
|
|
||||||
Conversation:
|
|
||||||
{chat_history}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SuggestionGeneratorInput = {
|
type SuggestionGeneratorInput = {
|
||||||
chat_history: BaseMessage[];
|
chatHistory: BaseMessage[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const outputParser = new ListLineOutputParser({
|
const outputParser = new ListLineOutputParser({
|
||||||
key: 'suggestions',
|
key: 'suggestions',
|
||||||
});
|
});
|
||||||
|
|
||||||
const createSuggestionGeneratorChain = (llm: BaseChatModel) => {
|
const generateSuggestions = async (
|
||||||
return RunnableSequence.from([
|
|
||||||
RunnableMap.from({
|
|
||||||
chat_history: (input: SuggestionGeneratorInput) =>
|
|
||||||
formatChatHistoryAsString(input.chat_history),
|
|
||||||
}),
|
|
||||||
PromptTemplate.fromTemplate(suggestionGeneratorPrompt),
|
|
||||||
llm,
|
|
||||||
outputParser,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateSuggestions = (
|
|
||||||
input: SuggestionGeneratorInput,
|
input: SuggestionGeneratorInput,
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
) => {
|
) => {
|
||||||
(llm as unknown as ChatOpenAI).temperature = 0;
|
const chatPrompt = await ChatPromptTemplate.fromMessages([
|
||||||
const suggestionGeneratorChain = createSuggestionGeneratorChain(llm);
|
new SystemMessage(suggestionGeneratorPrompt),
|
||||||
return suggestionGeneratorChain.invoke(input);
|
new HumanMessage(`<conversation>${formatChatHistoryAsString(input.chatHistory)}</conversation>`)
|
||||||
|
]).formatMessages({})
|
||||||
|
|
||||||
|
const res = await llm.invoke(chatPrompt)
|
||||||
|
|
||||||
|
const suggestions = await outputParser.invoke(res)
|
||||||
|
|
||||||
|
return suggestions
|
||||||
};
|
};
|
||||||
|
|
||||||
export default generateSuggestions;
|
export default generateSuggestions;
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ class GeminiProvider extends BaseModelProvider<GeminiConfig> {
|
|||||||
let defaultChatModels: Model[] = [];
|
let defaultChatModels: Model[] = [];
|
||||||
|
|
||||||
data.models.forEach((m: any) => {
|
data.models.forEach((m: any) => {
|
||||||
if (m.supportedGenerationMethods.some((genMethod: string) => genMethod === 'embedText' || genMethod === 'embedContent')) {
|
if (
|
||||||
|
m.supportedGenerationMethods.some(
|
||||||
|
(genMethod: string) =>
|
||||||
|
genMethod === 'embedText' || genMethod === 'embedContent',
|
||||||
|
)
|
||||||
|
) {
|
||||||
defaultEmbeddingModels.push({
|
defaultEmbeddingModels.push({
|
||||||
key: m.name,
|
key: m.name,
|
||||||
name: m.displayName,
|
name: m.displayName,
|
||||||
|
|||||||
26
src/lib/prompts/media/image.ts
Normal file
26
src/lib/prompts/media/image.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { BaseMessageLike } from "@langchain/core/messages";
|
||||||
|
|
||||||
|
export const imageSearchPrompt = `
|
||||||
|
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.
|
||||||
|
Output only the rephrased query wrapped in an XML <query> element. Do not include any explanation or additional text.
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const imageSearchFewShots: BaseMessageLike[] = [
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is a cat?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>A cat</query>'],
|
||||||
|
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is a car? How does it work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>Car working</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>AC working</query>']
|
||||||
|
]
|
||||||
25
src/lib/prompts/media/videos.ts
Normal file
25
src/lib/prompts/media/videos.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { BaseMessageLike } from "@langchain/core/messages";
|
||||||
|
|
||||||
|
export const videoSearchPrompt = `
|
||||||
|
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 <query> element. Do not include any explanation or additional text.
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const videoSearchFewShots: BaseMessageLike[] = [
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does a car work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>How does a car work?</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nWhat is the theory of relativity?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>Theory of relativity</query>'],
|
||||||
|
[
|
||||||
|
'user',
|
||||||
|
'<conversation>\n</conversation>\n<follow_up>\nHow does an AC work?\n</follow_up>',
|
||||||
|
],
|
||||||
|
['assistant', '<query>AC working</query>'],
|
||||||
|
]
|
||||||
15
src/lib/prompts/suggestions/index.ts
Normal file
15
src/lib/prompts/suggestions/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const suggestionGeneratorPrompt = `
|
||||||
|
You are an AI suggestion generator for an AI powered search engine. You will be given a conversation below. You need to generate 4-5 suggestions based on the conversation. The suggestion should be relevant to the conversation that can be used by the user to ask the chat model for more information.
|
||||||
|
You need to make sure the suggestions are relevant to the conversation and are helpful to the user. Keep a note that the user might use these suggestions to ask a chat model for more information.
|
||||||
|
Make sure the suggestions are medium in length and are informative and relevant to the conversation.
|
||||||
|
|
||||||
|
Provide these suggestions separated by newlines between the XML tags <suggestions> and </suggestions>. For example:
|
||||||
|
|
||||||
|
<suggestions>
|
||||||
|
Tell me more about SpaceX and their recent projects
|
||||||
|
What is the latest news on SpaceX?
|
||||||
|
Who is the CEO of SpaceX?
|
||||||
|
</suggestions>
|
||||||
|
|
||||||
|
Today's date is ${new Date().toISOString()}
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user