jan/web/screens/Settings/Engines/RemoteEngineSettings.tsx
vansangpfiev 8fefe6e167
chore: tauri codesign and CI/CD (#4961)
* chore: build tauri

* chore: codesign tauri app

* chore: test update from electron to tauri

* chore: test update from electron to tauri

* chore: update csp config and cors

* chore: nightly to 1317

* fix: correct pre_install_path

* chore: jan-nightly to 1320

* chore: self sign tauri

* chore: CI/CD for Windows, Linux

commit 4897b2bcf7f044080fce81bd725515e62fc4eb29
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Fri Apr 25 15:32:37 2025 +0700

    chore: cleanup tauri config

commit 66c5676ec146b25c89cccb570ede7c070dbc5853
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Fri Apr 25 14:37:04 2025 +0700

    fix: store path

commit bc6560c576873e55f84c4b21764bedbdd9dbd5a8
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Thu Apr 24 09:39:50 2025 +0700

    chore: Linux CI

commit b036275dc9f1df7614aaca3b358b9c6493082512
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Wed Apr 23 16:41:22 2025 +0700

    chore: updater windows

commit e91b543dbdd82bd4a44db7550ffb993897b56081
Merge: dea80a83 4a54a378
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Wed Apr 23 16:39:24 2025 +0700

    Merge branch 'chore/tauri-cicd' of https://github.com/menloresearch/jan into chore/tauri-cicd-windows

commit dea80a83966113b108137c385a3c28920d2adda4
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Wed Apr 23 11:47:04 2025 +0700

    chore: update azuresigntool install method

commit 2ec2234082be57e53887192153fa982a134ea535
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Wed Apr 23 11:01:31 2025 +0700

    chore: add verbose option build tauri and targets app and dmg for macos build

commit 42c7592cc89641130545551d4d864268cde3d5b0
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Wed Apr 23 10:35:27 2025 +0700

    chore: update targets build

commit 4c8ba44ff60cdef8b639fa189f5729dc69c5aff6
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Wed Apr 23 09:53:21 2025 +0700

    refactor: remove debug step and upload electron build artifact

commit 158c08b465e18823e0f2b9a30fd5ecd589d08934
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Wed Apr 23 09:21:08 2025 +0700

    chore: add script codesign on windows

commit 4545b2bcd852029472298e530176494992dd0950
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Tue Apr 22 13:39:49 2025 +0700

    chore: update csp setting

commit f64a1e1ca958e3c1c685485a06d45956ddcf14a0
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Tue Apr 22 10:15:14 2025 +0700

    chore: update azuresigntool installation

commit 1f4b9d18b332d5205685a6fe68f5dfaf973d273c
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Tue Apr 22 09:49:42 2025 +0700

    chore: update signcommand

commit 911a3ab3540f872f6fe906c8e2135440d39f108c
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 19:19:23 2025 +0700

    chore: update codesign tauri windows

commit fba15c4c2de43b4cb87308ef998cdd8dc88b1ce6
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 19:04:29 2025 +0700

    chore: update path azuresigntool

commit 8b8c950b56f5aa42baf76aba064fc99b50758150
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 18:38:56 2025 +0700

    chore: update azuresigntool path

commit bd67a2b7908b5f3a126c634a840e0b941373a3c6
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 17:47:33 2025 +0700

    chore: update azuresigntool url

commit f70effca7c09cd2fe9b5866b4f194b64a13294b9
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 17:33:32 2025 +0700

    chore: update azuretoolsign download

commit 667910772f30369b9afa554ad06e4378f93d0b1a
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 16:56:25 2025 +0700

    chore: update path azuresigntool

commit f1610bfd80dfa996db4a777bb58475f2e6d02cc6
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 16:52:36 2025 +0700

    chore: update azuresigntool path

commit 0873d56fb88fb66c884eff31d3f63aa99858f038
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 16:19:46 2025 +0700

    chore: add debug step

commit 88e0b1a697ed478375429686eb1c03ae71a3b447
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 15:58:39 2025 +0700

    ci(windows): download AzureSignTool to src-tauri for Tauri code signing

commit 47f94e86589826c3941a3d602298f188d6480980
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 15:21:20 2025 +0700

    fix: AzureSignTool signcommand Path

commit dc014a7905fd0b49b5972e24b4d5773c5dc29ea5
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 15:00:02 2025 +0700

    chore: add debug step azuresigntool tauri windows

commit ee7b6163a8419604dfba7dc2f967026be4884da4
Author: Minh141120 <minh.itptit@gmail.com>
Date:   Mon Apr 21 14:33:33 2025 +0700

    chore: tauri windows codesign

commit 6607090857120531d8a096f45ff556c3f2553e53
Author: vansangpfiev <vansangpfiev@gmail.com>
Date:   Thu Apr 17 10:29:50 2025 +0700

    chore: add windows download script

commit 4b1a5cc29c77eecca75978a1ab3126d2c710e738
Author: Nguyen Ngoc Minh <kuuhakuu@Nguyens-MacBook-Air.local>
Date:   Mon Apr 21 13:44:34 2025 +0700

    chore: tauri codesign windows

* chore: workflows for tauri

* chore: test tauri manual build

* chore: hide windows install detail

* chore: upload artifacts

* feat: run mcp with bundled bun and uv

* chore: clean up

* chore: update cicd

* chore: remove deprecated workflows

* chore: update allowed origins

* chore: pull binaries windows, linux (#4963)

* fix: get bun and uv from execution path

* fix: macos

* fix: typo

* fix: remove old Jan binaries on windows

* chore: build bun and uv universal

* fix: appimage bundle bun issue

* chore: libfuse2 for linux CI

* feat: tauri cicd preview (#4975)

* feat: tauri cicd preview

* chore: add suffix preview for tauri build

* chore: update condition for s3 upload nightly channel

* chore: add debug step for tauri macos preview

* chore: update aws s3 macos tauri build

* refactor: remove debug code

* chore: update artifact name tauri macos build

* chore: add tauri build step for electron beta and stable

* chore: update preview

* chore: bump llama.cpp engine to b5351

* chore: bump engine version

* fix: cors windows

---------

Co-authored-by: vansangpfiev <sang@jan.ai>
Co-authored-by: Service Account <service@jan.ai>
Co-authored-by: Louis <louis@jan.ai>
Co-authored-by: hiento09 <tominhhien97@gmail.com>
Co-authored-by: Nguyen Ngoc Minh <91668012+Minh141120@users.noreply.github.com>
2025-05-15 17:11:19 +07:00

482 lines
18 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useCallback, useRef, useState, useEffect } from 'react'
import {
EngineConfig as OriginalEngineConfig,
InferenceEngine,
events,
EngineEvent,
} from '@janhq/core'
interface EngineConfig extends OriginalEngineConfig {
[key: string]: any
}
import { ScrollArea, Input, TextArea, Button } from '@janhq/joi'
import { useAtomValue, useSetAtom } from 'jotai'
import { set } from 'lodash'
import {
ChevronDown,
ChevronRight,
Eye,
EyeOff,
RefreshCwIcon,
} from 'lucide-react'
import { twMerge } from 'tailwind-merge'
import Spinner from '@/containers/Loader/Spinner'
import {
builtInEngines,
normalizeBuiltInEngineName,
updateEngine,
useGetEngines,
useRefreshModelList,
} from '@/hooks/useEngineManagement'
import { getTitleByEngine } from '@/utils/modelEngine'
import { getLogoEngine } from '@/utils/modelEngine'
import ModalAddModel from './ModalAddModel'
import ModalDeleteModel from './ModalDeleteModel'
import {
downloadedModelsAtom,
selectedModelAtom,
} from '@/helpers/atoms/Model.atom'
import { showScrollBarAtom } from '@/helpers/atoms/Setting.atom'
import { threadsAtom } from '@/helpers/atoms/Thread.atom'
const RemoteEngineSettings = ({
engine: engineName,
}: {
engine: InferenceEngine
}) => {
const { engines, mutate } = useGetEngines()
const downloadedModels = useAtomValue(downloadedModelsAtom)
const [showApiKey, setShowApiKey] = useState(false)
const remoteModels = downloadedModels.filter((e) => e.engine === engineName)
const [isActiveAdvanceSetting, setisActiveAdvanceSetting] = useState(false)
const setSelectedModel = useSetAtom(selectedModelAtom)
const customEngineLogo = getLogoEngine(engineName)
const threads = useAtomValue(threadsAtom)
const { refreshingModels, refreshModels } = useRefreshModelList(engineName)
const showScrollBar = useAtomValue(showScrollBarAtom)
const engine =
engines &&
Object.entries(engines)
.filter(([key]) => key === engineName)
.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(engineName, updatedEngine)
mutate()
events.emit(EngineEvent.OnEngineUpdate, {})
}, 300)
},
[engine, engineName, 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 (threads.length === 0) {
setSelectedModel(remoteModels[0])
}
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])
if (!engine) return null
return (
<ScrollArea
type={showScrollBar ? 'always' : 'scroll'}
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))]">
{!customEngineLogo ? (
<span>
Enter your authentication key to activate this engine.{' '}
</span>
) : (
<span>
Enter your authentication key to activate this engine.
{engine.engine && engine.url && (
<span>
&nbsp;Get your API key from{' '}
<a
target="_blank"
href={engine.url}
className="text-[hsla(var(--app-link))]"
>
{getTitleByEngine(engine.engine)}.
</a>
</span>
)}
</span>
)}
</p>
</div>
<div className="w-full">
<div className="relative">
{data?.api_key.length > 0 && (
<div className="absolute right-4 top-1/2 z-10 -translate-y-1/2">
<div
className="cursor-pointer"
onClick={() => setShowApiKey(!showApiKey)}
>
{showApiKey ? (
<EyeOff
size={14}
className="text-[hsla(var(--text-seconday))]"
/>
) : (
<Eye
size={14}
className="text-[hsla(var(--text-seconday))]"
/>
)}
</div>
</div>
)}
<Input
placeholder="Enter API Key"
type={showApiKey ? 'text' : 'password'}
value={data?.api_key}
className="pr-10"
onChange={(e) => handleChange('api_key', e.target.value)}
/>
</div>
</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="mb-4 flex items-center justify-between gap-x-2">
<div>
<h6 className="mb-2 line-clamp-1 font-semibold">Model</h6>
</div>
<div className="flex gap-2">
<Button
theme={'ghost'}
variant={'outline'}
onClick={() => refreshModels(engineName)}
>
{refreshingModels ? (
<Spinner size={16} strokeWidth={2} className="mr-2" />
) : (
<RefreshCwIcon size={16} className="mr-2" />
)}
Refresh
</Button>
<ModalAddModel engine={engineName} />
</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>
<ModalDeleteModel model={item} />
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
</div>
<div className="px-4 pb-4">
<p
className="flex cursor-pointer items-center text-sm font-medium text-[hsla(var(--text-secondary))]"
onClick={() => setisActiveAdvanceSetting(!isActiveAdvanceSetting)}
>
<span>Advanced Settings</span>
<span>
{isActiveAdvanceSetting ? (
<ChevronDown size={14} className="ml-1" />
) : (
<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">
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 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 endpoint URL to fetch available models.
</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>
{!builtInEngines.includes(
normalizeBuiltInEngineName(engineName) ?? ''
) && (
<>
<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))]">
HTTP headers template required for API
authentication and version specification.
</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))]">
Template to transform OpenAI-compatible requests
into provider-specific 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))]">
Template to transform provider responses into
OpenAI-compatible 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