feat: add copilot setting to config
refactor: move getConfig to actions, extracted Settings
This commit is contained in:
parent
d788ca8eba
commit
79d4d87f24
10 changed files with 140 additions and 113 deletions
|
@ -1,54 +1,55 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {parse, stringify} from "smol-toml";
|
||||
import { parse, stringify } from 'smol-toml';
|
||||
|
||||
const configFileName = 'config.toml';
|
||||
|
||||
interface Config {
|
||||
GENERAL: {
|
||||
PORT: number;
|
||||
SIMILARITY_MEASURE: string;
|
||||
};
|
||||
API_KEYS: {
|
||||
OPENAI: string;
|
||||
GROQ: string;
|
||||
};
|
||||
API_ENDPOINTS: {
|
||||
SEARXNG: string;
|
||||
OLLAMA: string;
|
||||
};
|
||||
MODELS: [
|
||||
{
|
||||
"name": string;
|
||||
"api_key": string;
|
||||
"base_url": string;
|
||||
"provider": string;
|
||||
}
|
||||
];
|
||||
EMBEDDINGS: [
|
||||
{
|
||||
"name": string;
|
||||
"model": string;
|
||||
"api_key": string;
|
||||
"base_url": string;
|
||||
"provider": string;
|
||||
}
|
||||
];
|
||||
GENERAL: {
|
||||
PORT: number;
|
||||
SIMILARITY_MEASURE: string;
|
||||
ENABLE_COPILOT: boolean;
|
||||
};
|
||||
API_KEYS: {
|
||||
OPENAI: string;
|
||||
GROQ: string;
|
||||
};
|
||||
API_ENDPOINTS: {
|
||||
SEARXNG: string;
|
||||
OLLAMA: string;
|
||||
};
|
||||
MODELS: [
|
||||
{
|
||||
name: string;
|
||||
api_key: string;
|
||||
base_url: string;
|
||||
provider: string;
|
||||
},
|
||||
];
|
||||
EMBEDDINGS: [
|
||||
{
|
||||
name: string;
|
||||
model: string;
|
||||
api_key: string;
|
||||
base_url: string;
|
||||
provider: string;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
type RecursivePartial<T> = {
|
||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||
[P in keyof T]?: RecursivePartial<T[P]>;
|
||||
};
|
||||
|
||||
const loadConfig = () =>
|
||||
parse(
|
||||
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
||||
) as any as Config;
|
||||
parse(
|
||||
fs.readFileSync(path.join(__dirname, `../${configFileName}`), 'utf-8'),
|
||||
) as any as Config;
|
||||
|
||||
export const getPort = () => loadConfig().GENERAL.PORT;
|
||||
|
||||
export const getSimilarityMeasure = () =>
|
||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
||||
|
||||
export const getOpenaiApiKey = () => loadConfig().API_KEYS.OPENAI;
|
||||
|
||||
|
@ -62,18 +63,17 @@ export const getCustomModels = () => loadConfig().MODELS;
|
|||
|
||||
export const getCustomEmbeddingModels = () => loadConfig().EMBEDDINGS;
|
||||
|
||||
export const getCopilotEnabled = () => loadConfig().GENERAL.ENABLE_COPILOT;
|
||||
|
||||
export const updateConfig = (config: RecursivePartial<Config>) => {
|
||||
const currentConfig = loadConfig();
|
||||
const currentConfig = loadConfig();
|
||||
|
||||
const updatedConfig = {
|
||||
...currentConfig,
|
||||
...config
|
||||
};
|
||||
const updatedConfig = {
|
||||
...currentConfig,
|
||||
...config,
|
||||
};
|
||||
|
||||
const toml = stringify(updatedConfig);
|
||||
const toml = stringify(updatedConfig);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `../${configFileName}`),
|
||||
toml,
|
||||
);
|
||||
fs.writeFileSync(path.join(__dirname, `../${configFileName}`), toml);
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
getAvailableEmbeddingModelProviders,
|
||||
} from '../lib/providers';
|
||||
import {
|
||||
getCopilotEnabled,
|
||||
getGroqApiKey,
|
||||
getOllamaApiEndpoint,
|
||||
getOpenaiApiKey,
|
||||
|
@ -38,6 +39,7 @@ router.get('/', async (_, res) => {
|
|||
config['openaiApiKey'] = getOpenaiApiKey();
|
||||
config['ollamaApiUrl'] = getOllamaApiEndpoint();
|
||||
config['groqApiKey'] = getGroqApiKey();
|
||||
config['copilotEnabled'] = getCopilotEnabled();
|
||||
|
||||
res.status(200).json(config);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ import Chat from './Chat';
|
|||
import EmptyChat from './EmptyChat';
|
||||
import { toast } from 'sonner';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { getSuggestions } from '@/lib/actions';
|
||||
import { getConfig, getSuggestions } from '@/lib/actions';
|
||||
|
||||
export type Message = {
|
||||
id: string;
|
||||
|
@ -156,6 +156,15 @@ const ChatWindow = () => {
|
|||
messagesRef.current = messages;
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
const config = await getConfig();
|
||||
localStorage.setItem('copilotEnabled', config.copilotEnabled.toString());
|
||||
};
|
||||
|
||||
fetchConfig();
|
||||
});
|
||||
|
||||
const sendMessage = async (message: string) => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
|
|
|
@ -12,8 +12,9 @@ const EmptyChatMessageInput = ({
|
|||
focusMode: string;
|
||||
setFocusMode: (mode: string) => void;
|
||||
}) => {
|
||||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
||||
const [copilotToggled, setCopilotToggled] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const copilotEnabled = localStorage.getItem('copilotEnabled') === 'true';
|
||||
|
||||
return (
|
||||
<form
|
||||
|
@ -45,10 +46,12 @@ const EmptyChatMessageInput = ({
|
|||
{/* <Attach /> */}
|
||||
</div>
|
||||
<div className="flex flex-row items-center space-x-4 -mx-2">
|
||||
<CopilotToggle
|
||||
copilotEnabled={copilotEnabled}
|
||||
setCopilotEnabled={setCopilotEnabled}
|
||||
/>
|
||||
{copilotEnabled && (
|
||||
<CopilotToggle
|
||||
copilotToggled={copilotToggled}
|
||||
setCopilotToggled={setCopilotToggled}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
disabled={message.trim().length === 0}
|
||||
className="bg-[#24A0ED] text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full p-2"
|
||||
|
|
|
@ -7,11 +7,10 @@ import { cn } from '@/lib/utils';
|
|||
import {
|
||||
BookCopy,
|
||||
Disc3,
|
||||
Share,
|
||||
Volume2,
|
||||
StopCircle,
|
||||
Layers3,
|
||||
Plus,
|
||||
StopCircle,
|
||||
Volume2,
|
||||
} from 'lucide-react';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import Copy from './MessageActions/Copy';
|
||||
|
@ -157,6 +156,7 @@ const MessageBox = ({
|
|||
onClick={() => {
|
||||
sendMessage(suggestion);
|
||||
}}
|
||||
role="button"
|
||||
className="cursor-pointer flex flex-row justify-between font-medium space-x-2 items-center"
|
||||
>
|
||||
<p className="transition duration-200 hover:text-[#24A0ED]">
|
||||
|
|
|
@ -11,7 +11,7 @@ const MessageInput = ({
|
|||
sendMessage: (message: string) => void;
|
||||
loading: boolean;
|
||||
}) => {
|
||||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
||||
const [copilotToggled, setCopilotToggled] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [textareaRows, setTextareaRows] = useState(1);
|
||||
const [mode, setMode] = useState<'multi' | 'single'>('single');
|
||||
|
@ -57,8 +57,8 @@ const MessageInput = ({
|
|||
{mode === 'single' && (
|
||||
<div className="flex flex-row items-center space-x-4">
|
||||
<CopilotToggle
|
||||
copilotEnabled={copilotEnabled}
|
||||
setCopilotEnabled={setCopilotEnabled}
|
||||
copilotToggled={copilotToggled}
|
||||
setCopilotToggled={setCopilotToggled}
|
||||
/>
|
||||
<button
|
||||
disabled={message.trim().length === 0 || loading}
|
||||
|
@ -73,8 +73,8 @@ const MessageInput = ({
|
|||
<Attach />
|
||||
<div className="flex flex-row items-center space-x-4">
|
||||
<CopilotToggle
|
||||
copilotEnabled={copilotEnabled}
|
||||
setCopilotEnabled={setCopilotEnabled}
|
||||
copilotToggled={copilotToggled}
|
||||
setCopilotToggled={setCopilotToggled}
|
||||
/>
|
||||
<button
|
||||
disabled={message.trim().length === 0 || loading}
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
import {BadgePercent, ChevronDown, CopyPlus, Globe, Pencil, ScanEye, SwatchBook,} from 'lucide-react';
|
||||
import {cn} from '@/lib/utils';
|
||||
import {Popover, Switch, Transition} from '@headlessui/react';
|
||||
import {SiReddit, SiYoutube} from '@icons-pack/react-simple-icons';
|
||||
import {Fragment} from 'react';
|
||||
import {
|
||||
BadgePercent,
|
||||
ChevronDown,
|
||||
CopyPlus,
|
||||
Globe,
|
||||
Pencil,
|
||||
ScanEye,
|
||||
SwatchBook,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Popover, Switch, Transition } from '@headlessui/react';
|
||||
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
export const Attach = () => {
|
||||
return (
|
||||
|
@ -46,9 +54,9 @@ const focusModes = [
|
|||
description: 'Search and watch videos',
|
||||
icon: (
|
||||
<SiYoutube
|
||||
className="h-5 w-auto mr-0.5"
|
||||
onPointerEnter={undefined}
|
||||
onPointerLeave={undefined}
|
||||
className="h-5 w-auto mr-0.5"
|
||||
onPointerEnter={undefined}
|
||||
onPointerLeave={undefined}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -58,9 +66,9 @@ const focusModes = [
|
|||
description: 'Search for discussions and opinions',
|
||||
icon: (
|
||||
<SiReddit
|
||||
className="h-5 w-auto mr-0.5"
|
||||
onPointerEnter={undefined}
|
||||
onPointerLeave={undefined}
|
||||
className="h-5 w-auto mr-0.5"
|
||||
onPointerEnter={undefined}
|
||||
onPointerLeave={undefined}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -133,23 +141,23 @@ export const Focus = ({
|
|||
};
|
||||
|
||||
export const CopilotToggle = ({
|
||||
copilotEnabled,
|
||||
setCopilotEnabled,
|
||||
copilotToggled,
|
||||
setCopilotToggled,
|
||||
}: {
|
||||
copilotEnabled: boolean;
|
||||
setCopilotEnabled: (enabled: boolean) => void;
|
||||
copilotToggled: boolean;
|
||||
setCopilotToggled: (enabled: boolean) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="group flex flex-row items-center space-x-1 active:scale-95 duration-200 transition cursor-pointer">
|
||||
<Switch
|
||||
checked={copilotEnabled}
|
||||
onChange={setCopilotEnabled}
|
||||
checked={copilotToggled}
|
||||
onChange={setCopilotToggled}
|
||||
className="bg-[#111111] border border-[#1C1C1C] relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full"
|
||||
>
|
||||
<span className="sr-only">Copilot</span>
|
||||
<span
|
||||
className={cn(
|
||||
copilotEnabled
|
||||
copilotToggled
|
||||
? 'translate-x-6 bg-[#24A0ED]'
|
||||
: 'translate-x-1 bg-white/50',
|
||||
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
||||
|
@ -157,10 +165,10 @@ export const CopilotToggle = ({
|
|||
/>
|
||||
</Switch>
|
||||
<p
|
||||
onClick={() => setCopilotEnabled(!copilotEnabled)}
|
||||
onClick={() => setCopilotToggled(!copilotToggled)}
|
||||
className={cn(
|
||||
'text-xs font-medium transition-colors duration-150 ease-in-out',
|
||||
copilotEnabled
|
||||
copilotToggled
|
||||
? 'text-[#24A0ED]'
|
||||
: 'text-white/50 group-hover:text-white',
|
||||
)}
|
||||
|
|
|
@ -1,18 +1,8 @@
|
|||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
interface SettingsType {
|
||||
chatModelProviders: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
embeddingModelProviders: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
openaiApiKey: string;
|
||||
groqApiKey: string;
|
||||
ollamaApiUrl: string;
|
||||
}
|
||||
import { Settings } from '@/types/Settings';
|
||||
import { getConfig } from '@/lib/actions';
|
||||
|
||||
const SettingsDialog = ({
|
||||
isOpen,
|
||||
|
@ -21,7 +11,7 @@ const SettingsDialog = ({
|
|||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
}) => {
|
||||
const [config, setConfig] = useState<SettingsType | null>(null);
|
||||
const [config, setConfig] = useState<Settings | null>(null);
|
||||
const [selectedChatModelProvider, setSelectedChatModelProvider] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
@ -42,13 +32,7 @@ const SettingsDialog = ({
|
|||
if (isOpen) {
|
||||
const fetchConfig = async () => {
|
||||
setIsLoading(true);
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const data = (await res.json()) as SettingsType;
|
||||
const data = await getConfig();
|
||||
setConfig(data);
|
||||
|
||||
const chatModelProvidersKeys = Object.keys(
|
||||
|
@ -66,30 +50,28 @@ const SettingsDialog = ({
|
|||
: '';
|
||||
|
||||
const chatModelProvider =
|
||||
localStorage.getItem('chatModelProvider') ||
|
||||
defaultChatModelProvider ||
|
||||
(localStorage.getItem('chatModelProvider') ??
|
||||
defaultChatModelProvider) ||
|
||||
'';
|
||||
const chatModel =
|
||||
localStorage.getItem('chatModel') ||
|
||||
(data.chatModelProviders &&
|
||||
data.chatModelProviders[chatModelProvider]?.[0]) ||
|
||||
(localStorage.getItem('chatModel') ??
|
||||
data.chatModelProviders?.[chatModelProvider]?.[0]) ||
|
||||
'';
|
||||
const embeddingModelProvider =
|
||||
localStorage.getItem('embeddingModelProvider') ||
|
||||
defaultEmbeddingModelProvider ||
|
||||
(localStorage.getItem('embeddingModelProvider') ??
|
||||
defaultEmbeddingModelProvider) ||
|
||||
'';
|
||||
const embeddingModel =
|
||||
localStorage.getItem('embeddingModel') ||
|
||||
(data.embeddingModelProviders &&
|
||||
data.embeddingModelProviders[embeddingModelProvider]?.[0]) ||
|
||||
(localStorage.getItem('embeddingModel') ??
|
||||
data.embeddingModelProviders?.[embeddingModelProvider]?.[0]) ||
|
||||
'';
|
||||
|
||||
setSelectedChatModelProvider(chatModelProvider);
|
||||
setSelectedChatModel(chatModel);
|
||||
setSelectedEmbeddingModelProvider(embeddingModelProvider);
|
||||
setSelectedEmbeddingModel(embeddingModel);
|
||||
setCustomOpenAIApiKey(localStorage.getItem('openAIApiKey') || '');
|
||||
setCustomOpenAIBaseURL(localStorage.getItem('openAIBaseURL') || '');
|
||||
setCustomOpenAIApiKey(localStorage.getItem('openAIApiKey') ?? '');
|
||||
setCustomOpenAIBaseURL(localStorage.getItem('openAIBaseURL') ?? '');
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Message } from '@/components/ChatWindow';
|
||||
import { Settings } from '@/types/Settings';
|
||||
|
||||
export const getSuggestions = async (chatHisory: Message[]) => {
|
||||
export const getSuggestions = async (chatHistory: Message[]) => {
|
||||
const chatModel = localStorage.getItem('chatModel');
|
||||
const chatModelProvider = localStorage.getItem('chatModelProvider');
|
||||
|
||||
|
@ -10,7 +11,7 @@ export const getSuggestions = async (chatHisory: Message[]) => {
|
|||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_history: chatHisory,
|
||||
chat_history: chatHistory,
|
||||
chat_model: chatModel,
|
||||
chat_model_provider: chatModelProvider,
|
||||
}),
|
||||
|
@ -20,3 +21,13 @@ export const getSuggestions = async (chatHisory: Message[]) => {
|
|||
|
||||
return data.suggestions;
|
||||
};
|
||||
|
||||
export async function getConfig() {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
return (await res.json()) as Settings;
|
||||
}
|
||||
|
|
12
ui/types/Settings.ts
Normal file
12
ui/types/Settings.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export interface Settings {
|
||||
chatModelProviders: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
embeddingModelProviders: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
openaiApiKey: string;
|
||||
groqApiKey: string;
|
||||
ollamaApiUrl: string;
|
||||
copilotEnabled: boolean;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue