mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2026-01-12 06:45:42 +00:00
Compare commits
6 Commits
046f159528
...
4fc810d976
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fc810d976 | ||
|
|
a548fd694a | ||
|
|
2c61f47088 | ||
|
|
1c0e90c8e0 | ||
|
|
ee5d9172a4 | ||
|
|
c35b684dc5 |
@@ -29,7 +29,6 @@
|
|||||||
"axios": "^1.8.3",
|
"axios": "^1.8.3",
|
||||||
"better-sqlite3": "^11.9.1",
|
"better-sqlite3": "^11.9.1",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"compute-cosine-similarity": "^1.1.0",
|
|
||||||
"drizzle-orm": "^0.40.1",
|
"drizzle-orm": "^0.40.1",
|
||||||
"framer-motion": "^12.23.24",
|
"framer-motion": "^12.23.24",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
|
|||||||
@@ -9,38 +9,30 @@ type CalculationWidgetProps = {
|
|||||||
|
|
||||||
const Calculation = ({ expression, result }: CalculationWidgetProps) => {
|
const Calculation = ({ expression, result }: CalculationWidgetProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 overflow-hidden shadow-sm">
|
<div className="rounded-lg border border-light-200 dark:border-dark-200">
|
||||||
<div className="flex items-center gap-2 p-3 bg-light-100/50 dark:bg-dark-100/50 border-b border-light-200 dark:border-dark-200">
|
<div className="p-4 space-y-4">
|
||||||
<div className="rounded-full p-1.5 bg-light-100 dark:bg-dark-100">
|
<div className="space-y-2">
|
||||||
<Calculator className="w-4 h-4 text-black/70 dark:text-white/70" />
|
<div className="flex items-center gap-2 text-black/60 dark:text-white/70">
|
||||||
</div>
|
<Calculator className="w-4 h-4" />
|
||||||
<span className="text-sm font-medium text-black dark:text-white">
|
<span className="text-xs uppercase font-semibold tracking-wide">
|
||||||
Calculation
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 space-y-3">
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-1.5 mb-1.5">
|
|
||||||
<span className="text-xs text-black/50 dark:text-white/50 font-medium">
|
|
||||||
Expression
|
Expression
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-light-100 dark:bg-dark-100 rounded-md p-2.5 border border-light-200 dark:border-dark-200">
|
<div className="rounded-lg border border-light-200 dark:border-dark-200 bg-light-secondary dark:bg-dark-secondary p-3">
|
||||||
<code className="text-sm text-black dark:text-white font-mono break-all">
|
<code className="text-sm text-black dark:text-white font-mono break-all">
|
||||||
{expression}
|
{expression}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5 mb-1.5">
|
<div className="flex items-center gap-2 text-black/60 dark:text-white/70">
|
||||||
<Equal className="w-3.5 h-3.5 text-black/50 dark:text-white/50" />
|
<Equal className="w-4 h-4" />
|
||||||
<span className="text-xs text-black/50 dark:text-white/50 font-medium">
|
<span className="text-xs uppercase font-semibold tracking-wide">
|
||||||
Result
|
Result
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gradient-to-br from-light-100 to-light-secondary dark:from-dark-100 dark:to-dark-secondary rounded-md p-4 border-2 border-light-200 dark:border-dark-200">
|
<div className="rounded-xl border border-light-200 dark:border-dark-200 bg-light-secondary dark:bg-dark-secondary p-5">
|
||||||
<div className="text-4xl font-bold text-black dark:text-white font-mono tabular-nums">
|
<div className="text-4xl font-bold text-black dark:text-white font-mono tabular-nums">
|
||||||
{result.toLocaleString()}
|
{result.toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
GenerateTextOutput,
|
GenerateTextOutput,
|
||||||
StreamTextOutput,
|
StreamTextOutput,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { Ollama } from 'ollama';
|
import { Ollama, Tool as OllamaTool } from 'ollama';
|
||||||
import { parse } from 'partial-json';
|
import { parse } from 'partial-json';
|
||||||
|
|
||||||
type OllamaConfig = {
|
type OllamaConfig = {
|
||||||
@@ -36,9 +36,23 @@ class OllamaLLM extends BaseLLM<OllamaConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async generateText(input: GenerateTextInput): Promise<GenerateTextOutput> {
|
async generateText(input: GenerateTextInput): Promise<GenerateTextOutput> {
|
||||||
|
const ollamaTools: OllamaTool[] = [];
|
||||||
|
|
||||||
|
input.tools?.forEach((tool) => {
|
||||||
|
ollamaTools.push({
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: z.toJSONSchema(tool.schema).properties,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const res = await this.ollamaClient.chat({
|
const res = await this.ollamaClient.chat({
|
||||||
model: this.config.model,
|
model: this.config.model,
|
||||||
messages: input.messages,
|
messages: input.messages,
|
||||||
|
tools: ollamaTools.length > 0 ? ollamaTools : undefined,
|
||||||
options: {
|
options: {
|
||||||
top_p: input.options?.topP ?? this.config.options?.topP,
|
top_p: input.options?.topP ?? this.config.options?.topP,
|
||||||
temperature:
|
temperature:
|
||||||
@@ -58,6 +72,11 @@ class OllamaLLM extends BaseLLM<OllamaConfig> {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
content: res.message.content,
|
content: res.message.content,
|
||||||
|
toolCalls:
|
||||||
|
res.message.tool_calls?.map((tc) => ({
|
||||||
|
name: tc.function.name,
|
||||||
|
arguments: tc.function.arguments,
|
||||||
|
})) || [],
|
||||||
additionalInfo: {
|
additionalInfo: {
|
||||||
reasoning: res.message.thinking,
|
reasoning: res.message.thinking,
|
||||||
},
|
},
|
||||||
@@ -67,10 +86,24 @@ class OllamaLLM extends BaseLLM<OllamaConfig> {
|
|||||||
async *streamText(
|
async *streamText(
|
||||||
input: GenerateTextInput,
|
input: GenerateTextInput,
|
||||||
): AsyncGenerator<StreamTextOutput> {
|
): AsyncGenerator<StreamTextOutput> {
|
||||||
|
const ollamaTools: OllamaTool[] = [];
|
||||||
|
|
||||||
|
input.tools?.forEach((tool) => {
|
||||||
|
ollamaTools.push({
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: z.toJSONSchema(tool.schema) as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const stream = await this.ollamaClient.chat({
|
const stream = await this.ollamaClient.chat({
|
||||||
model: this.config.model,
|
model: this.config.model,
|
||||||
messages: input.messages,
|
messages: input.messages,
|
||||||
stream: true,
|
stream: true,
|
||||||
|
tools: ollamaTools.length > 0 ? ollamaTools : undefined,
|
||||||
options: {
|
options: {
|
||||||
top_p: input.options?.topP ?? this.config.options?.topP,
|
top_p: input.options?.topP ?? this.config.options?.topP,
|
||||||
temperature:
|
temperature:
|
||||||
@@ -91,6 +124,11 @@ class OllamaLLM extends BaseLLM<OllamaConfig> {
|
|||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
yield {
|
yield {
|
||||||
contentChunk: chunk.message.content,
|
contentChunk: chunk.message.content,
|
||||||
|
toolCallChunk:
|
||||||
|
chunk.message.tool_calls?.map((tc) => ({
|
||||||
|
name: tc.function.name,
|
||||||
|
arguments: tc.function.arguments,
|
||||||
|
})) || [],
|
||||||
done: chunk.done,
|
done: chunk.done,
|
||||||
additionalInfo: {
|
additionalInfo: {
|
||||||
reasoning: chunk.message.thinking,
|
reasoning: chunk.message.thinking,
|
||||||
|
|||||||
@@ -7,8 +7,16 @@ import {
|
|||||||
GenerateTextInput,
|
GenerateTextInput,
|
||||||
GenerateTextOutput,
|
GenerateTextOutput,
|
||||||
StreamTextOutput,
|
StreamTextOutput,
|
||||||
|
ToolCall,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { parse } from 'partial-json';
|
import { parse } from 'partial-json';
|
||||||
|
import z from 'zod';
|
||||||
|
import {
|
||||||
|
ChatCompletionMessageParam,
|
||||||
|
ChatCompletionTool,
|
||||||
|
ChatCompletionToolMessageParam,
|
||||||
|
} from 'openai/resources/index.mjs';
|
||||||
|
import { Message } from '@/lib/types';
|
||||||
|
|
||||||
type OpenAIConfig = {
|
type OpenAIConfig = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
@@ -29,10 +37,38 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertToOpenAIMessages(messages: Message[]): ChatCompletionMessageParam[] {
|
||||||
|
return messages.map((msg) => {
|
||||||
|
if (msg.role === 'tool') {
|
||||||
|
return {
|
||||||
|
role: 'tool',
|
||||||
|
tool_call_id: msg.id,
|
||||||
|
content: msg.content,
|
||||||
|
} as ChatCompletionToolMessageParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async generateText(input: GenerateTextInput): Promise<GenerateTextOutput> {
|
async generateText(input: GenerateTextInput): Promise<GenerateTextOutput> {
|
||||||
|
const openaiTools: ChatCompletionTool[] = [];
|
||||||
|
|
||||||
|
input.tools?.forEach((tool) => {
|
||||||
|
openaiTools.push({
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: z.toJSONSchema(tool.schema),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const response = await this.openAIClient.chat.completions.create({
|
const response = await this.openAIClient.chat.completions.create({
|
||||||
model: this.config.model,
|
model: this.config.model,
|
||||||
messages: input.messages,
|
tools: openaiTools.length > 0 ? openaiTools : undefined,
|
||||||
|
messages: this.convertToOpenAIMessages(input.messages),
|
||||||
temperature:
|
temperature:
|
||||||
input.options?.temperature ?? this.config.options?.temperature ?? 1.0,
|
input.options?.temperature ?? this.config.options?.temperature ?? 1.0,
|
||||||
top_p: input.options?.topP ?? this.config.options?.topP,
|
top_p: input.options?.topP ?? this.config.options?.topP,
|
||||||
@@ -49,6 +85,18 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
|
|||||||
if (response.choices && response.choices.length > 0) {
|
if (response.choices && response.choices.length > 0) {
|
||||||
return {
|
return {
|
||||||
content: response.choices[0].message.content!,
|
content: response.choices[0].message.content!,
|
||||||
|
toolCalls:
|
||||||
|
response.choices[0].message.tool_calls
|
||||||
|
?.map((tc) => {
|
||||||
|
if (tc.type === 'function') {
|
||||||
|
return {
|
||||||
|
name: tc.function.name,
|
||||||
|
id: tc.id,
|
||||||
|
arguments: JSON.parse(tc.function.arguments),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((tc) => tc !== undefined) || [],
|
||||||
additionalInfo: {
|
additionalInfo: {
|
||||||
finishReason: response.choices[0].finish_reason,
|
finishReason: response.choices[0].finish_reason,
|
||||||
},
|
},
|
||||||
@@ -61,9 +109,23 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
|
|||||||
async *streamText(
|
async *streamText(
|
||||||
input: GenerateTextInput,
|
input: GenerateTextInput,
|
||||||
): AsyncGenerator<StreamTextOutput> {
|
): AsyncGenerator<StreamTextOutput> {
|
||||||
|
const openaiTools: ChatCompletionTool[] = [];
|
||||||
|
|
||||||
|
input.tools?.forEach((tool) => {
|
||||||
|
openaiTools.push({
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: z.toJSONSchema(tool.schema),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const stream = await this.openAIClient.chat.completions.create({
|
const stream = await this.openAIClient.chat.completions.create({
|
||||||
model: this.config.model,
|
model: this.config.model,
|
||||||
messages: input.messages,
|
messages: this.convertToOpenAIMessages(input.messages),
|
||||||
|
tools: openaiTools.length > 0 ? openaiTools : undefined,
|
||||||
temperature:
|
temperature:
|
||||||
input.options?.temperature ?? this.config.options?.temperature ?? 1.0,
|
input.options?.temperature ?? this.config.options?.temperature ?? 1.0,
|
||||||
top_p: input.options?.topP ?? this.config.options?.topP,
|
top_p: input.options?.topP ?? this.config.options?.topP,
|
||||||
@@ -78,10 +140,33 @@ class OpenAILLM extends BaseLLM<OpenAIConfig> {
|
|||||||
stream: true,
|
stream: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let recievedToolCalls: { name: string; id: string; arguments: string }[] =
|
||||||
|
[];
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
if (chunk.choices && chunk.choices.length > 0) {
|
if (chunk.choices && chunk.choices.length > 0) {
|
||||||
|
const toolCalls = chunk.choices[0].delta.tool_calls;
|
||||||
yield {
|
yield {
|
||||||
contentChunk: chunk.choices[0].delta.content || '',
|
contentChunk: chunk.choices[0].delta.content || '',
|
||||||
|
toolCallChunk:
|
||||||
|
toolCalls?.map((tc) => {
|
||||||
|
if (tc.type === 'function') {
|
||||||
|
const call = {
|
||||||
|
name: tc.function?.name!,
|
||||||
|
id: tc.id!,
|
||||||
|
arguments: tc.function?.arguments || '',
|
||||||
|
};
|
||||||
|
recievedToolCalls.push(call);
|
||||||
|
return { ...call, arguments: parse(call.arguments || '{}') };
|
||||||
|
} else {
|
||||||
|
const existingCall = recievedToolCalls[tc.index];
|
||||||
|
existingCall.arguments += tc.function?.arguments || '';
|
||||||
|
return {
|
||||||
|
...existingCall,
|
||||||
|
arguments: parse(existingCall.arguments),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}) || [],
|
||||||
done: chunk.choices[0].finish_reason !== null,
|
done: chunk.choices[0].finish_reason !== null,
|
||||||
additionalInfo: {
|
additionalInfo: {
|
||||||
finishReason: chunk.choices[0].finish_reason,
|
finishReason: chunk.choices[0].finish_reason,
|
||||||
|
|||||||
@@ -37,18 +37,33 @@ type GenerateOptions = {
|
|||||||
presencePenalty?: number;
|
presencePenalty?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Tool = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
schema: z.ZodObject<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToolCall = {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
arguments: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
type GenerateTextInput = {
|
type GenerateTextInput = {
|
||||||
messages: ChatTurnMessage[];
|
messages: ChatTurnMessage[];
|
||||||
|
tools?: Tool[];
|
||||||
options?: GenerateOptions;
|
options?: GenerateOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GenerateTextOutput = {
|
type GenerateTextOutput = {
|
||||||
content: string;
|
content: string;
|
||||||
|
toolCalls: ToolCall[];
|
||||||
additionalInfo?: Record<string, any>;
|
additionalInfo?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StreamTextOutput = {
|
type StreamTextOutput = {
|
||||||
contentChunk: string;
|
contentChunk: string;
|
||||||
|
toolCallChunk: Partial<ToolCall>[];
|
||||||
additionalInfo?: Record<string, any>;
|
additionalInfo?: Record<string, any>;
|
||||||
done?: boolean;
|
done?: boolean;
|
||||||
};
|
};
|
||||||
@@ -83,4 +98,6 @@ export type {
|
|||||||
GenerateObjectInput,
|
GenerateObjectInput,
|
||||||
GenerateObjectOutput,
|
GenerateObjectOutput,
|
||||||
StreamObjectOutput,
|
StreamObjectOutput,
|
||||||
|
Tool,
|
||||||
|
ToolCall,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ export type ChatTurnMessage = {
|
|||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ToolMessage = {
|
||||||
|
role: 'tool';
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Message = ChatTurnMessage | ToolMessage;
|
||||||
|
|
||||||
export type Chunk = {
|
export type Chunk = {
|
||||||
content: string;
|
content: string;
|
||||||
metadata: Record<string, any>;
|
metadata: Record<string, any>;
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
import cosineSimilarity from 'compute-cosine-similarity';
|
|
||||||
|
|
||||||
const computeSimilarity = (x: number[], y: number[]): number => {
|
const computeSimilarity = (x: number[], y: number[]): number => {
|
||||||
return cosineSimilarity(x, y) as number;
|
if (x.length !== y.length)
|
||||||
|
throw new Error('Vectors must be of the same length');
|
||||||
|
|
||||||
|
let dotProduct = 0;
|
||||||
|
let normA = 0;
|
||||||
|
let normB = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < x.length; i++) {
|
||||||
|
dotProduct += x[i] * y[i];
|
||||||
|
normA += x[i] * x[i];
|
||||||
|
normB += y[i] * y[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normA === 0 || normB === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default computeSimilarity;
|
export default computeSimilarity;
|
||||||
|
|||||||
Reference in New Issue
Block a user