Skip to content

Commit

Permalink
Merge pull request #94 from Royal-lobster/dynamic-modals
Browse files Browse the repository at this point in the history
  • Loading branch information
Royal-lobster authored Oct 26, 2024
2 parents dbfbbc6 + 8df3cc0 commit b6a2378
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 232 deletions.
6 changes: 6 additions & 0 deletions .changeset/eight-melons-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"syncia": patch
---

- Dynamic modal names from /modals
- Deprecates usage of ollama modals directly, we can now use them via openai compatible endpoint
71 changes: 11 additions & 60 deletions src/components/Settings/Sections/ChatSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import * as Switch from '@radix-ui/react-switch'
import React, { useState } from 'react'
import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai'
import { useSettings } from '../../../hooks/useSettings'
import { Mode } from '../../../config/settings'
import { useChatModels } from '../../../hooks/useChatModels'
import { useSettings } from '../../../hooks/useSettings'
import { capitalizeText } from '../../../lib/capitalizeText'
import { validateApiKey } from '../../../lib/validApiKey'
import FieldWrapper from '../Elements/FieldWrapper'
import SectionHeading from '../Elements/SectionHeading'
import { type AvailableModels, Mode } from '../../../config/settings'
import { getReadableModelName } from '../../../lib/getReadableModelName'

const ChatSettings = () => {
const [settings, setSettings] = useSettings()
const [showPassword, setShowPassword] = useState(false)
const { availableModels, fetchLocalModels } = useChatModels()
const { models, setActiveChatModel } = useChatModels()
const OpenAiApiKeyInputRef = React.useRef<HTMLInputElement>(null)
const OpenAiBaseUrlInputRef = React.useRef<HTMLInputElement>(null)

Expand All @@ -23,10 +21,8 @@ const ChatSettings = () => {
event: React.FormEvent<HTMLFormElement>,
) => {
event.preventDefault()
const target = event.target as HTMLFormElement

const apiKeyValue = target.openAiApiKey.value
const baseurlValue = target.openAiBaseUrl.value
const apiKeyValue = OpenAiApiKeyInputRef.current?.value || ''
const baseurlValue = OpenAiBaseUrlInputRef.current?.value || ''

if (OpenAiApiKeyInputRef.current) {
const isOpenAiKeyValid: boolean = await validateApiKey(
Expand Down Expand Up @@ -120,69 +116,24 @@ const ChatSettings = () => {
Update
</button>
</div>
</FieldWrapper>{' '}
{/* =========================
Model Setting
===========================*/}
<FieldWrapper
title="Show Local Models"
description="Show local models in the model selection via ollama (https://ollama.com/) which allows you to use open source models that run on your machine."
row={true}
>
<Switch.Root
checked={chatSettings.showLocalModels}
onCheckedChange={(value) => {
setSettings({
...settings,
chat: {
...chatSettings,
showLocalModels: value,
},
})
fetchLocalModels()
}}
className="cdx-w-[42px] cdx-h-[25px] cdx-bg-neutral-500 cdx-rounded-full cdx-relative data-[state=checked]:cdx-bg-blue-500 cdx-outline-none cdx-cursor-default"
>
<Switch.Thumb className="cdx-block cdx-w-[21px] cdx-h-[21px] cdx-bg-white cdx-rounded-full cdx-transition-transform cdx-duration-100 cdx-translate-x-0.5 cdx-will-change-transform data-[state=checked]:cdx-translate-x-[19px]" />
</Switch.Root>
</FieldWrapper>
{chatSettings.showLocalModels && (
<div>
🚧 NOTE: You must run this command for this to work:
<code className="cdx-block dark:cdx-bg-white/10 cdx-bg-black/10 cdx-rounded cdx-mt-2 cdx-p-2">
OLLAMA_ORIGINS=
{window.location.origin} ollama start
</code>
</div>
)}
<FieldWrapper
title="Model"
description="Choose between OpenAI Chat Modals. For more information, visit https://platform.openai.com/docs/models/overview"
description="Choose between available chat models"
row={true}
>
<select
value={chatSettings.model}
value={chatSettings.model || ''}
className="input cdx-w-44"
onChange={(e) => {
setSettings({
...settings,
chat: {
...chatSettings,
model: e.target.value as AvailableModels,
},
})
}}
onChange={(e) => setActiveChatModel(e.target.value)}
>
{availableModels.map(([model, value]) => (
<option key={model} value={value}>
{getReadableModelName(model)}
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.id}
</option>
))}
</select>
</FieldWrapper>
{/* =========================
Mode Setting
===========================*/}
<FieldWrapper
title="Mode"
description="Tweak temperature of response. Creative will generate more non deterministic responses, Precise will generate more deterministic responses."
Expand Down
161 changes: 89 additions & 72 deletions src/components/Sidebar/auth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,68 @@
import React, { useEffect } from 'react'
import type React from 'react'
import { useEffect, useState } from 'react'
import { useChatModels } from '../../../hooks/useChatModels'
import { useSettings } from '../../../hooks/useSettings'
import { validateApiKey } from '../../../lib/validApiKey'

const Auth = () => {
const [, setSettings] = useSettings()
const [error, setError] = React.useState<string | null>(null)
const [showAdvanced, setShowAdvanced] = React.useState(false)
const { models, setActiveChatModel, fetchAvailableModels } = useChatModels()
const [isLoadingModels, setIsLoadingModels] = useState(false)
const [error, setError] = useState<string | null>(null)
const [formData, setFormData] = useState({
apiKey: '',
baseUrl: 'https://api.openai.com/v1',
})

useEffect(() => {
if (error) {
setTimeout(() => {
setError(null)
}, 3000)
const timer = setTimeout(() => setError(null), 3000)
return () => clearTimeout(timer)
}
}, [error])

const handleOpenAiKeySubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = new FormData(e.currentTarget)
const key = data.get('openAiKey') as string | null
const openAiBaseUrl =
(data.get('openAiBaseUrl') as string) || 'https://api.openai.com/v1'
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const { id, value } = e.target
const newFormData = { ...formData, [id]: value }
setFormData(newFormData)

if (id === 'apiKey' && value.startsWith('sk-') && value.length > 40) {
await validateAndUpdateSettings(value, formData.baseUrl)
} else if (id === 'baseUrl' && formData.apiKey) {
await validateAndUpdateSettings(formData.apiKey, value)
}
}

if (key && (await validateApiKey(key, openAiBaseUrl))) {
setSettings((prev) => ({
...prev,
chat: {
...prev.chat,
openAIKey: key as string,
openAiBaseUrl: openAiBaseUrl,
},
}))
} else {
setError('Invalid API key. Please try with a valid one.')
const validateAndUpdateSettings = async (key: string, url: string) => {
setIsLoadingModels(true)
try {
if (await validateApiKey(key, url)) {
setSettings((prev) => ({
...prev,
chat: { ...prev.chat, openAIKey: key, openAiBaseUrl: url },
}))
await fetchAvailableModels()
} else {
setError('Invalid API key. Please try with a valid one.')
}
} finally {
setIsLoadingModels(false)
}
}

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!formData.apiKey || !formData.baseUrl) {
setError('Please fill in all required fields')
return
}
validateAndUpdateSettings(formData.apiKey, formData.baseUrl)
}

return (
<form
onSubmit={handleOpenAiKeySubmit}
className="cdx-flex cdx-flex-col cdx-p-6 cdx-text-center cdx-justify-center cdx-items-center cdx-h-full"
className="cdx-flex cdx-flex-col cdx-p-6 cdx-justify-center cdx-items-center cdx-h-full"
onSubmit={(e) => e.preventDefault()}
>
<div className="cdx-text-2xl cdx-mt-48">Enter your OpenAI API key</div>
<div className="cdx-text-sm cdx-text-gray-400 cdx-mt-2">
Expand All @@ -53,76 +76,70 @@ const Auth = () => {
here
</a>
</div>
<div className="cdx-text-sm cdx-text-gray-400 cdx-mt-2">
It should look something like this:
</div>
<div className="cdx-text-sm cdx-text-gray-400 cdx-mt-2">
sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
</div>

<div className="cdx-w-full cdx-mt-6">
<label
htmlFor="openAiKey"
className="cdx-block cdx-text-center cdx-text-sm cdx-text-gray-500 dark:cdx-text-gray-400 cdx-mb-2"
>
API Key
</label>
<input
id="openAiKey"
name="openAiKey"
placeholder="Paste your OpenAI API key"
data-error={error ? 'true' : undefined}
className="cdx-text-center cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90 focus:cdx-outline-none focus:cdx-ring-2 focus:cdx-ring-blue-900 focus:cdx-ring-opacity-50 data-[error]:cdx-text-red-500"
id="apiKey"
value={formData.apiKey}
onChange={handleInputChange}
placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
className="cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90"
/>
</div>

<button
type="button"
onClick={() => setShowAdvanced(!showAdvanced)}
className="cdx-mt-4 cdx-text-sm cdx-text-blue-400 hover:cdx-underline"
>
{showAdvanced ? 'Hide Advanced Settings' : 'Show Advanced Settings'}
</button>
<div className="cdx-w-full cdx-mt-4">
<input
id="baseUrl"
value={formData.baseUrl}
onChange={handleInputChange}
placeholder="https://api.openai.com/v1"
className="cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90"
/>
</div>

{showAdvanced && (
<div className="cdx-w-full cdx-mt-4">
<label
htmlFor="openAiBaseUrl"
className="cdx-block cdx-text-center cdx-text-sm cdx-text-gray-500 dark:cdx-text-gray-400 cdx-mb-2"
>
Base URL (optional)
</label>
<input
id="openAiBaseUrl"
name="openAiBaseUrl"
placeholder="https://api.openai.com/v1"
defaultValue="https://api.openai.com/v1"
className="cdx-text-center cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90 focus:cdx-outline-none focus:cdx-ring-2 focus:cdx-ring-blue-900 focus:cdx-ring-opacity-50"
/>
</div>
)}
<div className="cdx-w-full cdx-mt-4">
<select
onChange={(e) => setActiveChatModel(e.target.value)}
disabled={!models.length}
className="cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90 disabled:cdx-opacity-50"
>
{isLoadingModels ? (
<option>Loading models...</option>
) : models.length ? (
models.map((model) => (
<option key={model.id} value={model.id}>
{model.id}
</option>
))
) : (
<option>Add API key to load models</option>
)}
</select>
</div>

{error && (
<div className="cdx-text-sm cdx-text-red-500 cdx-mt-2">{error}</div>
)}

<button
type="submit"
className="cdx-mt-4 cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90 focus:cdx-outline-none focus:cdx-ring-2 focus:cdx-ring-blue-900 focus:cdx-ring-opacity-50"
type="button"
disabled={isLoadingModels}
onClick={handleSubmit}
className="cdx-mt-4 cdx-p-2 cdx-w-full cdx-rounded-md cdx-border dark:cdx-border-neutral-600 cdx-border-neutral-200 dark:cdx-bg-neutral-800/90 cdx-bg-neutral-200/90 disabled:cdx-opacity-50"
>
Submit
{isLoadingModels ? 'Loading...' : 'Submit'}
</button>

<div className="cdx-text-sm cdx-text-gray-400 cdx-mt-2">
(Note: we only store your key locally. We do not send it anywhere. You
<div className="cdx-text-sm cdx-text-gray-400 cdx-mt-4">
Note: we only store your key locally. We do not send it anywhere. You
can check the{' '}
<a
href="https://github.com/Royal-lobster/Syncia"
className="cdx-text-blue-400"
>
source code
</a>{' '}
and inspect network tab to verify this.)
and inspect network tab to verify this.
</div>
</form>
)
Expand Down
17 changes: 7 additions & 10 deletions src/components/Sidebar/chat/ChangeChatModel.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { BsRobot } from 'react-icons/bs'
import type { AvailableModels } from '../../../config/settings'
import { useChatModels } from '../../../hooks/useChatModels'
import { getReadableModelName } from '../../../lib/getReadableModelName'

const ChangeChatModel = () => {
const { availableModels, activeChatModel, setActiveChatModel } =
useChatModels()
const { models, activeChatModel, setActiveChatModel } = useChatModels()
return (
<div className="cdx-flex cdx-items-center cdx-gap-1 cdx-text-neutral-500 dark:cdx-bg-neutral-900 cdx-bg-neutral-200 cdx-border cdx-rounded-md cdx-border-neutral-400/30 dark:cdx-border-neutral-500/30 cdx-py-1 cdx-px-3">
<BsRobot size={18} className="cdx-flex-shrink-0" />
<select
value={activeChatModel}
className="cdx-bg-transparent !m-0 !p-0 cdx-box-border cdx-w-min focus:cdx-outline-none focus:cdx-ring-1"
value={activeChatModel || ''}
className="cdx-bg-transparent !m-0 !p-0 cdx-box-border cdx-w-min focus:cdx-outline-none focus:cdx-ring-1 cdx-max-w-[100px]"
onChange={(e) => {
setActiveChatModel(e.target.value as AvailableModels)
setActiveChatModel(e.target.value)
}}
>
{availableModels.map(([model, value]) => (
<option key={model} value={value}>
{getReadableModelName(model)}
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.id}
</option>
))}
</select>
Expand Down
10 changes: 3 additions & 7 deletions src/components/Sidebar/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ChatList from './ChatList'
import { SidebarInput } from './ChatInput'
import { useChatCompletion } from '../../../hooks/useChatCompletion'
import { SYSTEM_PROMPT } from '../../../config/prompts'
import { AvailableModels, type Settings } from '../../../config/settings'
import type { Settings } from '../../../config/settings'

interface ChatProps {
settings: Settings
Expand All @@ -19,7 +19,7 @@ const Chat = ({ settings }: ChatProps) => {
removeMessagePair,
error,
} = useChatCompletion({
model: settings.chat.model,
model: settings.chat.model!,
apiKey: settings.chat.openAIKey!,
mode: settings.chat.mode,
systemPrompt: SYSTEM_PROMPT,
Expand Down Expand Up @@ -58,11 +58,7 @@ const Chat = ({ settings }: ChatProps) => {
clearMessages={clearMessages}
cancelRequest={cancelRequest}
isWebpageContextOn={settings.general.webpageContext}
isVisionModel={
settings.chat.model === AvailableModels.GPT_4_TURBO ||
settings.chat.model === AvailableModels.GPT_4O ||
settings.chat.model === AvailableModels.GPT_4O_MINI
}
isVisionModel={true}
/>
</>
)
Expand Down
Loading

0 comments on commit b6a2378

Please sign in to comment.