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:
Willie Zutz 2025-05-23 18:03:35 -06:00
parent 288120dc1d
commit c47a630372
17 changed files with 2142 additions and 818 deletions

View file

@ -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') {

View file

@ -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,
},
},

View file

@ -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">