feat(app): Introduce quality mode. Improve functionality of balanced mode using readability to get page content and pull relevant excerpts
feat(UI): Show progress during inferrence feat(security): Don't show API keys in the UI any more feat(models): Support Claude 4 Anthropic models
This commit is contained in:
parent
288120dc1d
commit
c47a630372
17 changed files with 2142 additions and 818 deletions
|
|
@ -18,10 +18,7 @@ import { ChatOpenAI } from '@langchain/openai';
|
|||
import crypto from 'crypto';
|
||||
import { and, eq, gt } from 'drizzle-orm';
|
||||
import { EventEmitter } from 'stream';
|
||||
import {
|
||||
registerCancelToken,
|
||||
cleanupCancelToken,
|
||||
} from '@/lib/cancel-tokens';
|
||||
import { registerCancelToken, cleanupCancelToken } from '@/lib/cancel-tokens';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
|
@ -115,6 +112,21 @@ const handleEmitterEvents = async (
|
|||
modelName: '',
|
||||
};
|
||||
|
||||
stream.on('progress', (data) => {
|
||||
const parsedData = JSON.parse(data);
|
||||
if (parsedData.type === 'progress') {
|
||||
writer.write(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'progress',
|
||||
data: parsedData.data,
|
||||
messageId: aiMessageId,
|
||||
}) + '\n',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('stats', (data) => {
|
||||
const parsedData = JSON.parse(data);
|
||||
if (parsedData.type === 'modelStats') {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export const GET = async (req: Request) => {
|
|||
|
||||
// Helper function to obfuscate API keys
|
||||
const protectApiKey = (key: string | null | undefined) => {
|
||||
return key ? "protected" : key;
|
||||
return key ? 'protected' : key;
|
||||
};
|
||||
|
||||
// Obfuscate all API keys in the response
|
||||
|
|
@ -85,39 +85,57 @@ export const POST = async (req: Request) => {
|
|||
try {
|
||||
const config = await req.json();
|
||||
|
||||
const getUpdatedProtectedValue = (newValue: string, currentConfig: string) => {
|
||||
const getUpdatedProtectedValue = (
|
||||
newValue: string,
|
||||
currentConfig: string,
|
||||
) => {
|
||||
if (newValue === 'protected') {
|
||||
return currentConfig;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
};
|
||||
|
||||
const updatedConfig = {
|
||||
MODELS: {
|
||||
OPENAI: {
|
||||
API_KEY: getUpdatedProtectedValue(config.openaiApiKey, getOpenaiApiKey()),
|
||||
API_KEY: getUpdatedProtectedValue(
|
||||
config.openaiApiKey,
|
||||
getOpenaiApiKey(),
|
||||
),
|
||||
},
|
||||
GROQ: {
|
||||
API_KEY: getUpdatedProtectedValue(config.groqApiKey, getGroqApiKey()),
|
||||
},
|
||||
ANTHROPIC: {
|
||||
API_KEY: getUpdatedProtectedValue(config.anthropicApiKey, getAnthropicApiKey()),
|
||||
API_KEY: getUpdatedProtectedValue(
|
||||
config.anthropicApiKey,
|
||||
getAnthropicApiKey(),
|
||||
),
|
||||
},
|
||||
GEMINI: {
|
||||
API_KEY: getUpdatedProtectedValue(config.geminiApiKey, getGeminiApiKey()),
|
||||
API_KEY: getUpdatedProtectedValue(
|
||||
config.geminiApiKey,
|
||||
getGeminiApiKey(),
|
||||
),
|
||||
},
|
||||
OLLAMA: {
|
||||
API_URL: config.ollamaApiUrl,
|
||||
},
|
||||
DEEPSEEK: {
|
||||
API_KEY: getUpdatedProtectedValue(config.deepseekApiKey, getDeepseekApiKey()),
|
||||
API_KEY: getUpdatedProtectedValue(
|
||||
config.deepseekApiKey,
|
||||
getDeepseekApiKey(),
|
||||
),
|
||||
},
|
||||
LM_STUDIO: {
|
||||
API_URL: config.lmStudioApiUrl,
|
||||
},
|
||||
CUSTOM_OPENAI: {
|
||||
API_URL: config.customOpenaiApiUrl,
|
||||
API_KEY: getUpdatedProtectedValue(config.customOpenaiApiKey, getCustomOpenaiApiKey()),
|
||||
API_KEY: getUpdatedProtectedValue(
|
||||
config.customOpenaiApiKey,
|
||||
getCustomOpenaiApiKey(),
|
||||
),
|
||||
MODEL_NAME: config.customOpenaiModelName,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { Settings as SettingsIcon, ArrowLeft, Loader2, Info } from 'lucide-react';
|
||||
import {
|
||||
Settings as SettingsIcon,
|
||||
ArrowLeft,
|
||||
Loader2,
|
||||
Info,
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Switch } from '@headlessui/react';
|
||||
|
|
@ -128,7 +133,10 @@ const SettingsSection = ({
|
|||
<h2 className="text-black/90 dark:text-white/90 font-medium">{title}</h2>
|
||||
{tooltip && (
|
||||
<div className="relative group">
|
||||
<Info size={16} className="text-black/70 dark:text-white/70 cursor-help" />
|
||||
<Info
|
||||
size={16}
|
||||
className="text-black/70 dark:text-white/70 cursor-help"
|
||||
/>
|
||||
<div className="absolute left-1/2 -translate-x-1/2 bottom-full mb-2 px-3 py-2 bg-black/90 dark:bg-white/90 text-white dark:text-black text-xs rounded-lg opacity-0 group-hover:opacity-100 whitespace-nowrap transition-opacity">
|
||||
{tooltip}
|
||||
</div>
|
||||
|
|
@ -238,7 +246,7 @@ const Page = () => {
|
|||
fetchConfig();
|
||||
}, []);
|
||||
|
||||
const saveConfig = async (key: string, value: any) => {
|
||||
const saveConfig = async (key: string, value: any) => {
|
||||
setSavingStates((prev) => ({ ...prev, [key]: true }));
|
||||
|
||||
try {
|
||||
|
|
@ -798,8 +806,8 @@ const Page = () => {
|
|||
)}
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
title="API Keys"
|
||||
<SettingsSection
|
||||
title="API Keys"
|
||||
tooltip="API Key values can be viewed in the config.toml file"
|
||||
>
|
||||
<div className="flex flex-col space-y-4">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue