jan/web/screens/Settings/Engines/RemoteEngineSettings.tsx
Faisal Amir 2a0601f75a
feat: remote engine management (#4364)
* feat: remote engine management

* chore: fix linter issue

* chore: remove unused imports

* fix: populate engines, models and legacy settings (#4403)

* fix: populate engines, models and legacy settings

* chore: legacy logics update configured remote engine

* fix: check exist path before reading

* fix: engines and models persist - race condition

* chore: update issue state

* test: update test cases

* chore: bring back Cortex extension settings

* chore: setup button gear / plus based apikey

* chore: fix remote engine from welcome screen

* chore: resolve linter issue

* chore: support request headers template

* chore: update engines using header_template instead of api_key_template

* chore: update models on changes

* fix: anthropic response template

* chore: fix welcome screen and debounce update value input

* chore: update engines list on changes

* chore: update engines list on change

* chore: update desc form add modal remote engines

* chore: bump cortex version to latest RC

* chore: fix linter

* fix: transform payload of Anthropic and OpenAI

* fix: typo

* fix: openrouter model id for auto routing

* chore: remove remote engine URL setting

* chore: add cohere engine and model support

* fix: should not clean on app launch - models list display issue

* fix: local engine check logic

* chore: bump app version to latest release 0.5.13

* test: fix failed tests

---------

Co-authored-by: Louis <louis@jan.ai>
2025-01-14 17:29:56 +07:00

366 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/no-unescaped-entities */
import React, { useCallback, useRef, useState, useEffect } from 'react'
import {
EngineConfig as OriginalEngineConfig,
InferenceEngine,
} from '@janhq/core'
interface EngineConfig extends OriginalEngineConfig {
[key: string]: any
}
import { ScrollArea, Input, TextArea } from '@janhq/joi'
import { useAtomValue } from 'jotai'
import { set } from 'lodash'
import { ChevronRight } from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import { updateEngine, useGetEngines } from '@/hooks/useEngineManagement'
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
const RemoteEngineSettings = ({
engine: name,
}: {
engine: InferenceEngine
}) => {
const { engines, mutate } = useGetEngines()
const downloadedModels = useAtomValue(downloadedModelsAtom)
const remoteModels = downloadedModels.filter((e) => e.engine === name)
const [isActiveAdvanceSetting, setisActiveAdvanceSetting] = useState(false)
const engine =
engines &&
Object.entries(engines)
.filter(([key]) => key === name)
.flatMap(([_, engineArray]) => engineArray as EngineConfig)[0]
const debounceRef = useRef<NodeJS.Timeout | null>(null)
const handleChange = useCallback(
(field: string, value: any) => {
if (!engine) return
setData((prevData) => {
const updatedData = { ...prevData }
set(updatedData, field, value)
return updatedData
})
if (debounceRef.current) {
clearTimeout(debounceRef.current)
}
debounceRef.current = setTimeout(async () => {
const updatedEngine = { ...engine }
set(updatedEngine, field, value)
await updateEngine(name, updatedEngine)
mutate()
}, 300)
},
[engine, name, mutate]
)
const [data, setData] = useState({
api_key: '',
metadata: {
header_template: '',
get_models_url: '',
transform_req: {
chat_completions: {
template: '',
url: '',
},
},
transform_resp: {
chat_completions: {
template: '',
},
},
},
})
useEffect(() => {
if (engine) {
setData({
api_key: engine.api_key || '',
metadata: {
header_template: engine.metadata?.header_template || '',
get_models_url: engine.metadata?.get_models_url || '',
transform_req: {
chat_completions: {
template:
engine.metadata?.transform_req?.chat_completions?.template ||
'',
url: engine.metadata?.transform_req?.chat_completions?.url || '',
},
},
transform_resp: {
chat_completions: {
template:
engine.metadata?.transform_resp?.chat_completions?.template ||
'',
},
},
},
})
}
}, [engine])
return (
<ScrollArea className="h-full w-full">
<div className="block w-full px-4">
<div className="mb-3 mt-4 border-b border-[hsla(var(--app-border))] pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">API Key</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
Enter your authentication key to activate this engine.
</p>
</div>
<div className="w-full">
<Input
placeholder="Enter API Key"
value={data?.api_key}
onChange={(e) => handleChange('api_key', e.target.value)}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 border-b border-[hsla(var(--app-border))] pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">
Chat Completion URL
</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
Enter your chat completion URL.
</p>
</div>
<div className="w-full">
<Input
placeholder="Enter Chat Completion URL"
value={data?.metadata.transform_req.chat_completions.url}
onChange={(e) =>
handleChange(
'metadata.transform_req.chat_completions.url',
e.target.value
)
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 ">
<div className="flex items-center justify-between gap-x-2">
<div>
<h6 className="mb-2 line-clamp-1 font-semibold">Model</h6>
</div>
</div>
<div>
{remoteModels &&
remoteModels?.map((item, i) => {
return (
<div
key={i}
className={twMerge(
'border border-b-0 border-[hsla(var(--app-border))] bg-[hsla(var(--tertiary-bg))] p-4 first:rounded-t-lg last:rounded-b-lg last:border-b',
remoteModels?.length === 1 && 'rounded-lg'
)}
>
<div className="flex flex-col items-start justify-start gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex w-full gap-x-8">
<div className="flex h-full w-full items-center justify-between gap-2">
<h6
className={twMerge(
'font-medium lg:line-clamp-1 lg:min-w-[280px] lg:max-w-[280px]',
'max-w-none text-[hsla(var(--text-secondary))]'
)}
>
{item.name}
</h6>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
</div>
<div className="px-4">
<p
className="flex cursor-pointer items-center text-sm font-medium text-[hsla(var(--text-secondary))]"
onClick={() => setisActiveAdvanceSetting(!isActiveAdvanceSetting)}
>
<span>Advance Settings</span>
<span>
<ChevronRight size={14} className="ml-1" />
</span>
</p>
</div>
{isActiveAdvanceSetting && (
<div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 border-b border-[hsla(var(--app-border))] pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">
Model List URL
</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
The base URL of the provider's API.
</p>
</div>
<div className="w-full">
<Input
placeholder="Enter model list URL"
value={data?.metadata?.get_models_url}
onChange={(e) =>
handleChange(
'metadata.get_models_url',
e.target.value
)
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 border-b border-[hsla(var(--app-border))] pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">
Request Headers Template
</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
Template for request headers format.
</p>
</div>
<div className="w-full">
<TextArea
placeholder="Enter headers template"
value={data?.metadata?.header_template}
onChange={(e) =>
handleChange(
'metadata.header_template',
e.target.value
)
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 border-b border-[hsla(var(--app-border))] pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">
Request Format Conversion
</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
Function to convert Jans request format to this engine
APIs format.
</p>
</div>
<div className="w-full">
<TextArea
placeholder="Enter conversion function"
value={
data?.metadata?.transform_req?.chat_completions
?.template
}
onChange={(e) =>
handleChange(
'metadata.transform_req.chat_completions.template',
e.target.value
)
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="block w-full px-4">
<div className="mb-3 mt-4 pb-4">
<div className="flex w-full flex-col items-start justify-between sm:flex-row">
<div className="w-full flex-shrink-0 space-y-1.5">
<div className="flex items-start justify-between gap-x-2">
<div className="w-full sm:w-3/4">
<h6 className="line-clamp-1 font-semibold">
Response Format Conversion
</h6>
<p className="mt-1 text-[hsla(var(--text-secondary))]">
Function to convert Jans request format to this engine
APIs format.
</p>
</div>
<div className="w-full">
<TextArea
placeholder="Enter conversion function"
value={
data?.metadata?.transform_resp?.chat_completions
?.template
}
onChange={(e) =>
handleChange(
'metadata.transform_resp.chat_completions.template',
e.target.value
)
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</ScrollArea>
)
}
export default RemoteEngineSettings