jan/web/screens/Settings/Engines/ModalAddRemoteEngine.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

214 lines
6.2 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.

import { memo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Button, Input, Modal, TextArea } from '@janhq/joi'
import { PlusIcon } from 'lucide-react'
import { z } from 'zod'
import { addRemoteEngine, useGetEngines } from '@/hooks/useEngineManagement'
const engineSchema = z.object({
engineName: z.string().min(1, 'Engine name is required'),
modelListUrl: z.string().url('Enter a valid Model List URL'),
headerTemplate: z.string().optional(),
apiKey: z.string().optional(),
requestFormat: z.string().optional(),
responseFormat: z.string().optional(),
})
const ModalAddRemoteEngine = () => {
const [open, setOpen] = useState(false)
const { mutate: mutateListEngines } = useGetEngines()
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(engineSchema),
defaultValues: {
engineName: '',
apiUrl: '',
modelListUrl: '',
headerTemplate: '',
apiKey: '',
requestFormat: '',
responseFormat: '',
},
})
const onSubmit = async (data: z.infer<typeof engineSchema>) => {
await addRemoteEngine({
type: 'remote',
engine: data.engineName,
api_key: data.apiKey,
metadata: {
header_template: data.headerTemplate,
get_models_url: data.modelListUrl,
transform_req: {
chat_completions: {
template: data.requestFormat,
},
},
transform_resp: {
chat_completions: {
template: data.requestFormat,
},
},
},
})
mutateListEngines()
setOpen(false)
}
// Helper to render labels with asterisks for required fields
const renderLabel = (label: string, isRequired: boolean, desc?: string) => (
<>
<span>
{label} {isRequired && <span className="text-red-500">*</span>}
</span>
<p className="mt-1 font-normal text-[hsla(var(--text-secondary))]">
{desc}
</p>
</>
)
return (
<Modal
title={
<div>
<p>Install Remote Engine</p>
<p className="text-sm font-normal text-[hsla(var(--text-secondary))]">
Only OpenAI API-compatible engines are supported
</p>
</div>
}
fullPage
open={open}
onOpenChange={() => setOpen(!open)}
trigger={
<Button>
<PlusIcon className="mr-2" size={14} />
Install Engine
</Button>
}
content={
<div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-2">
<label htmlFor="engineName" className="font-semibold">
{renderLabel('Engine Name', true)}
</label>
<Input
placeholder="Enter engine name"
{...register('engineName')}
/>
{errors.engineName && (
<p className="text-sm text-red-500">
{errors.engineName.message}
</p>
)}
</div>
<div className="space-y-2">
<label htmlFor="modelListUrl" className="font-semibold">
{renderLabel(
'Model List URL',
false,
`URL for fetching available models`
)}
</label>
<Input
placeholder="Enter model list URL"
{...register('modelListUrl')}
/>
{errors.modelListUrl && (
<p className="text-sm text-red-500">
{errors.modelListUrl.message}
</p>
)}
</div>
<div className="space-y-2">
<label htmlFor="apiKey" className="font-semibold">
{renderLabel(
'API Key',
false,
`Your authentication key from the provider`
)}
</label>
<Input
placeholder="Enter API Key"
type="password"
{...register('apiKey')}
/>
</div>
<div className="space-y-2">
<label htmlFor="headerTemplate" className="font-semibold">
{renderLabel(
'Request Headers Template',
false,
`Template for request headers format.`
)}
</label>
<TextArea
placeholder="Enter conversion function"
{...register('headerTemplate')}
/>
{errors.headerTemplate && (
<p className="text-sm text-red-500">
{errors.headerTemplate.message}
</p>
)}
</div>
<div className="space-y-2">
<label htmlFor="requestFormat" className="font-semibold">
{renderLabel(
'Request Format Conversion',
false,
`Function to convert Jans request format to this engine APIs format`
)}
</label>
<TextArea
placeholder="Enter conversion function"
{...register('requestFormat')}
/>
</div>
<div className="space-y-2">
<label htmlFor="responseFormat" className="font-semibold">
{renderLabel(
'Response Format Conversion',
false,
`Function to convert this engine APIs response format to Jans format`
)}
</label>
<TextArea
placeholder="Enter conversion function"
{...register('responseFormat')}
/>
</div>
<div className="mt-8 flex justify-end gap-x-2">
<Button
theme="ghost"
variant="outline"
onClick={() => setOpen(false)}
>
Cancel
</Button>
<Button type="submit">Install</Button>
</div>
</form>
</div>
}
/>
)
}
export default memo(ModalAddRemoteEngine)