Skip to content

Commit

Permalink
Merge pull request #80 from alexpong0630/main
Browse files Browse the repository at this point in the history
  • Loading branch information
Royal-lobster authored Oct 13, 2024
2 parents eea5062 + d2fc64c commit d0e71a0
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 34 deletions.
7 changes: 7 additions & 0 deletions .changeset/soft-snails-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"syncia": minor
---

- Support change Base URL
- Support gpt-4o-mini
- Scroll to bottom after message list updated
Binary file modified artifacts/chrome.zip
Binary file not shown.
51 changes: 36 additions & 15 deletions src/components/Settings/Sections/ChatSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ChatSettings = () => {
const [showPassword, setShowPassword] = useState(false)
const { availableModels, fetchLocalModels } = useChatModels()
const OpenAiApiKeyInputRef = React.useRef<HTMLInputElement>(null)
const OpenAiBaseUrlInputRef = React.useRef<HTMLInputElement>(null)

const chatSettings = settings.chat

Expand All @@ -23,29 +24,42 @@ const ChatSettings = () => {
) => {
event.preventDefault()
const target = event.target as HTMLFormElement
const input = target.querySelector('input') as HTMLInputElement
const value = input.value
setSettings({
...settings,
chat: {
...chatSettings,
openAIKey: value,
},
})

const apiKeyValue = target.openAiApiKey.value
const baseurlValue = target.openAiBaseUrl.value

if (OpenAiApiKeyInputRef.current) {
const isOpenAiKeyValid: boolean = await validateApiKey(value)

const isOpenAiKeyValid: boolean = await validateApiKey(apiKeyValue, baseurlValue)
if (!isOpenAiKeyValid) {
setSettings({
...settings,
chat: {
...chatSettings,
openAIKey: apiKeyValue,
openAiBaseUrl: baseurlValue ,
},
})
}
const inputStyles = isOpenAiKeyValid
? { classname: 'input-success', value: `✅ ${value}` }
: { classname: 'input-failed', value: `❌ ${value}` }
? { classname: 'input-success', value: `✅ ${apiKeyValue}` }
: { classname: 'input-failed', value: `❌ ${apiKeyValue}` }

OpenAiApiKeyInputRef.current.classList.add(inputStyles.classname)
OpenAiApiKeyInputRef.current.value = inputStyles.value
setTimeout(() => {
if (!OpenAiApiKeyInputRef.current) return
OpenAiApiKeyInputRef.current?.classList.remove(inputStyles.classname)
OpenAiApiKeyInputRef.current.value = value
OpenAiApiKeyInputRef.current.value = apiKeyValue
}, 2000)
}

if (OpenAiBaseUrlInputRef.current) {
OpenAiBaseUrlInputRef.current.classList.add('input-success')
OpenAiBaseUrlInputRef.current.value = `✅ ${baseurlValue}`
setTimeout(() => {
if (!OpenAiBaseUrlInputRef.current) return
OpenAiBaseUrlInputRef.current?.classList.remove('input-success')
OpenAiBaseUrlInputRef.current.value = baseurlValue
}, 2000)
}
}
Expand All @@ -62,13 +76,20 @@ const ChatSettings = () => {
<div className="cdx-relative cdx-w-full">
<input
required
pattern="^(sk(-proj)?-[a-zA-Z0-9]{48}$"
ref={OpenAiApiKeyInputRef}
name="openAiApiKey"
placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
defaultValue={chatSettings.openAIKey || ''}
type={showPassword ? 'text' : 'password'}
className="input"
/>
<input
ref={OpenAiBaseUrlInputRef}
name="openAiBaseUrl"
defaultValue={chatSettings.openAiBaseUrl || ''}
placeholder="Enter your OpenAI Base URL"
className="cdx-mt-4 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"
/>

<button
type="button"
Expand Down
10 changes: 8 additions & 2 deletions src/components/Sidebar/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ const Auth = () => {
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"

if (key && (await validateApiKey(key))) {
if (key && (await validateApiKey(key,openAiBaseUrl))) {
setSettings((prev) => ({
...prev,
chat: {
...prev.chat,
openAIKey: key as string,
openAiBaseUrl: openAiBaseUrl as string,
},
}))
} else {
Expand Down Expand Up @@ -59,7 +61,11 @@ const Auth = () => {
placeholder="Enter your OpenAI API key"
data-error={error ? 'true' : undefined}
className="cdx-mt-4 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"
pattern="sk-(proj-)?[a-zA-Z0-9]{48}"
/>
<input
name="openAiBaseUrl"
placeholder="Enter your OpenAI Base URL"
className="cdx-mt-4 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"
/>

{error && (
Expand Down
3 changes: 2 additions & 1 deletion src/components/Sidebar/chat/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ChatList = ({
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight
}
}, [])
}, [messages]) // Add messages as a dependency

const filteredMsgs = messages.filter((msg) => msg.role !== ChatRole.SYSTEM)

Expand Down Expand Up @@ -109,3 +109,4 @@ const ChatList = ({
}

export default ChatList

4 changes: 3 additions & 1 deletion src/components/Sidebar/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const Chat = ({ settings }: ChatProps) => {
apiKey: settings.chat.openAIKey!,
mode: settings.chat.mode,
systemPrompt: SYSTEM_PROMPT,
baseURL: settings.chat.openAiBaseUrl || "",
})

useEffect(() => {
Expand Down Expand Up @@ -59,7 +60,8 @@ const Chat = ({ settings }: ChatProps) => {
isWebpageContextOn={settings.general.webpageContext}
isVisionModel={
settings.chat.model === AvailableModels.GPT_4_TURBO ||
settings.chat.model === AvailableModels.GPT_4O
settings.chat.model === AvailableModels.GPT_4O ||
settings.chat.model === AvailableModels.GPT_4O_MINI
}
/>
</>
Expand Down
5 changes: 4 additions & 1 deletion src/config/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum AvailableModels {
GPT_4_TURBO = 'gpt-4-turbo',
GPT_4 = 'gpt-4',
GPT_3_5_TURBO = 'gpt-3.5-turbo',
GPT_4O_MINI = 'gpt-4o-mini',
}

export enum Mode {
Expand All @@ -31,6 +32,7 @@ export type Settings = {
model: AvailableModels
mode: Mode
showLocalModels: boolean
openAiBaseUrl: string | null
}
general: {
theme: ThemeOptions
Expand All @@ -46,9 +48,10 @@ export const defaultSettings: Settings = {
},
chat: {
openAIKey: null,
model: AvailableModels.GPT_4_TURBO,
model: AvailableModels.GPT_4O_MINI,
mode: Mode.BALANCED,
showLocalModels: false,
openAiBaseUrl: null,
},
general: {
theme: ThemeOptions.SYSTEM,
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/useChatCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface UseChatCompletionProps {
apiKey: string
mode: Mode
systemPrompt: string
baseURL: string
}

/**
Expand All @@ -36,6 +37,7 @@ export const useChatCompletion = ({
apiKey,
mode,
systemPrompt,
baseURL
}: UseChatCompletionProps) => {
const {
messages,
Expand All @@ -55,12 +57,15 @@ export const useChatCompletion = ({
streaming: true,
openAIApiKey: apiKey,
modelName: model,
configuration: {
baseURL: baseURL,
},
temperature: Number(mode),
maxTokens: 4_096,
})
}
return new Ollama({ model: model.replace('ollama-', '') })
}, [apiKey, model, mode])
}, [apiKey, model, mode, baseURL])

const previousMessages = messages.map((msg) => {
switch (msg.role) {
Expand Down Expand Up @@ -92,7 +97,7 @@ export const useChatCompletion = ({
*/
let matchedContext: string | undefined
if (context) {
matchedContext = await getMatchedContent(message.text, context, apiKey)
matchedContext = await getMatchedContent(message.text, context, apiKey, baseURL)
}

const expandedQuery = matchedContext
Expand Down
14 changes: 11 additions & 3 deletions src/lib/getMatchedContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export const getMatchedContent = async (
query: string,
context: string,
apiKey: string,
baseURL: string
) => {
const vectorStore = await getContextVectorStore(context, apiKey)
const vectorStore = await getContextVectorStore(context, apiKey,baseURL)
const retriever = vectorStore.asRetriever()
const relevantDocs = await retriever.getRelevantDocuments(query)
return relevantDocs.map((doc) => doc.pageContent).join('\n')
Expand All @@ -23,8 +24,15 @@ export const getMatchedContent = async (
* from the context. It caches the vector store in the local storage
* for faster retrieval
*/
const getContextVectorStore = async (context: string, apiKey: string) => {
const embeddings = new OpenAIEmbeddings({ openAIApiKey: apiKey })
const getContextVectorStore = async (context: string, apiKey: string, baseURL: string) => {
const embeddings = new OpenAIEmbeddings(
{
openAIApiKey: apiKey ,
configuration: {
baseURL: baseURL,
},
}
)
const hashKey = `SYNCIA_STORE_EMBEDDINGS_${await createSHA256Hash(context)}`
const memoryVectors: [] | null = JSON.parse(
localStorage.getItem(hashKey) || 'null',
Expand Down
19 changes: 11 additions & 8 deletions src/lib/getScreenshotImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const getScreenshotImage = async (): Promise<Blob> => {

// Create a canvas element and draw the screenshot on it
const canvas: HTMLCanvasElement = document.createElement('canvas')
canvas.width = endX - startX
canvas.height = endY - startY
canvas.width = endX > startX ? endX - startX : startX - endX
canvas.height = endY > startY ? endY - startY : startY - endY
const ctx: CanvasRenderingContext2D = canvas.getContext('2d')!
const image: HTMLImageElement = new Image()
image.src = screenshot
Expand All @@ -108,16 +108,19 @@ export const getScreenshotImage = async (): Promise<Blob> => {
})

// Crop the screenshot to the user's selection
let finalStartX = startX < endX ? startX : endX
let finalStaryY = startY < endY ? startY : endY

ctx.drawImage(
image,
startX * window.devicePixelRatio,
startY * window.devicePixelRatio,
(endX - startX) * window.devicePixelRatio,
(endY - startY) * window.devicePixelRatio,
finalStartX * window.devicePixelRatio,
finalStaryY * window.devicePixelRatio,
(canvas.width) * window.devicePixelRatio,
(canvas.height) * window.devicePixelRatio,
0,
0,
endX - startX,
endY - startY,
canvas.width,
canvas.height,
)

// Convert the canvas to a blob and return it
Expand Down
5 changes: 4 additions & 1 deletion src/lib/validApiKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { ChatOpenAI } from '@langchain/openai'

export const validateApiKey = async (
openAIApiKey: string,
baseURL: string
): Promise<boolean> => {
const model = new ChatOpenAI({ openAIApiKey })
const model = new ChatOpenAI({ openAIApiKey:openAIApiKey,configuration: {
baseURL: baseURL || "https://api.openai.com/v1",
},})
try {
await model.invoke([new HumanMessage('Say Ok')])
return true
Expand Down

0 comments on commit d0e71a0

Please sign in to comment.