feat: dynamically register extension settings (#2494)
* feat: add extesion settings Signed-off-by: James <james@jan.ai> --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
ec6bcf6357
commit
fa35aa6e14
@ -1,3 +1,7 @@
|
|||||||
|
import { SettingComponentProps } from '../types'
|
||||||
|
import { getJanDataFolderPath, joinPath } from './core'
|
||||||
|
import { fs } from './fs'
|
||||||
|
|
||||||
export enum ExtensionTypeEnum {
|
export enum ExtensionTypeEnum {
|
||||||
Assistant = 'assistant',
|
Assistant = 'assistant',
|
||||||
Conversational = 'conversational',
|
Conversational = 'conversational',
|
||||||
@ -32,6 +36,38 @@ export type InstallationState = InstallationStateTuple[number]
|
|||||||
* This class should be extended by any class that represents an extension.
|
* This class should be extended by any class that represents an extension.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseExtension implements ExtensionType {
|
export abstract class BaseExtension implements ExtensionType {
|
||||||
|
protected settingFolderName = 'settings'
|
||||||
|
protected settingFileName = 'settings.json'
|
||||||
|
|
||||||
|
/** @type {string} Name of the extension. */
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
/** @type {string} The URL of the extension to load. */
|
||||||
|
url: string
|
||||||
|
|
||||||
|
/** @type {boolean} Whether the extension is activated or not. */
|
||||||
|
active
|
||||||
|
|
||||||
|
/** @type {string} Extension's description. */
|
||||||
|
description
|
||||||
|
|
||||||
|
/** @type {string} Extension's version. */
|
||||||
|
version
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
url: string,
|
||||||
|
name?: string,
|
||||||
|
active?: boolean,
|
||||||
|
description?: string,
|
||||||
|
version?: string
|
||||||
|
) {
|
||||||
|
this.name = name
|
||||||
|
this.url = url
|
||||||
|
this.active = active
|
||||||
|
this.description = description
|
||||||
|
this.version = version
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the extension.
|
* Returns the type of the extension.
|
||||||
* @returns {ExtensionType} The type of the extension
|
* @returns {ExtensionType} The type of the extension
|
||||||
@ -40,11 +76,13 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is loaded.
|
* Called when the extension is loaded.
|
||||||
* Any initialization logic for the extension should be put here.
|
* Any initialization logic for the extension should be put here.
|
||||||
*/
|
*/
|
||||||
abstract onLoad(): void
|
abstract onLoad(): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is unloaded.
|
* Called when the extension is unloaded.
|
||||||
* Any cleanup logic for the extension should be put here.
|
* Any cleanup logic for the extension should be put here.
|
||||||
@ -67,6 +105,42 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerSettings(settings: SettingComponentProps[]): Promise<void> {
|
||||||
|
if (!this.name) {
|
||||||
|
console.error('Extension name is not defined')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionSettingFolderPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
this.name,
|
||||||
|
])
|
||||||
|
settings.forEach((setting) => {
|
||||||
|
setting.extensionName = this.name
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await fs.mkdir(extensionSettingFolderPath)
|
||||||
|
const settingFilePath = await joinPath([extensionSettingFolderPath, this.settingFileName])
|
||||||
|
|
||||||
|
if (await fs.existsSync(settingFilePath)) return
|
||||||
|
await fs.writeFileSync(settingFilePath, JSON.stringify(settings, null, 2))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSetting<T>(key: string, defaultValue: T) {
|
||||||
|
const keySetting = (await this.getSettings()).find((setting) => setting.key === key)
|
||||||
|
|
||||||
|
const value = keySetting?.controllerProps.value
|
||||||
|
return (value as T) ?? defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingUpdate<T>(key: string, value: T) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the prerequisites for the extension are installed.
|
* Determine if the prerequisites for the extension are installed.
|
||||||
*
|
*
|
||||||
@ -81,8 +155,59 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
async install(): Promise<void> {
|
||||||
async install(...args): Promise<void> {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSettings(): Promise<SettingComponentProps[]> {
|
||||||
|
if (!this.name) return []
|
||||||
|
|
||||||
|
const settingPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
this.settingFolderName,
|
||||||
|
this.name,
|
||||||
|
this.settingFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.readFileSync(settingPath, 'utf-8')
|
||||||
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
|
return settings
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSettings(componentProps: Partial<SettingComponentProps>[]): Promise<void> {
|
||||||
|
if (!this.name) return
|
||||||
|
|
||||||
|
const settings = await this.getSettings()
|
||||||
|
|
||||||
|
const updatedSettings = settings.map((setting) => {
|
||||||
|
const updatedSetting = componentProps.find(
|
||||||
|
(componentProp) => componentProp.key === setting.key
|
||||||
|
)
|
||||||
|
if (updatedSetting && updatedSetting.controllerProps) {
|
||||||
|
setting.controllerProps.value = updatedSetting.controllerProps.value
|
||||||
|
}
|
||||||
|
return setting
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
this.settingFolderName,
|
||||||
|
this.name,
|
||||||
|
this.settingFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
await fs.writeFileSync(settingPath, JSON.stringify(updatedSettings, null, 2))
|
||||||
|
|
||||||
|
updatedSettings.forEach((setting) => {
|
||||||
|
this.onSettingUpdate<typeof setting.controllerProps.value>(
|
||||||
|
setting.key,
|
||||||
|
setting.controllerProps.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
/*
|
/*
|
||||||
* Inference request
|
* Inference request
|
||||||
*/
|
*/
|
||||||
override inference(data: MessageRequest) {
|
override async inference(data: MessageRequest) {
|
||||||
if (data.model?.engine?.toString() !== this.provider) return
|
if (data.model?.engine?.toString() !== this.provider) return
|
||||||
|
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
@ -77,12 +77,14 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
...data.model,
|
...data.model,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header = await this.headers()
|
||||||
|
|
||||||
requestInference(
|
requestInference(
|
||||||
this.inferenceUrl,
|
this.inferenceUrl,
|
||||||
data.messages ?? [],
|
data.messages ?? [],
|
||||||
model,
|
model,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.headers()
|
header
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (content: any) => {
|
next: (content: any) => {
|
||||||
const messageContent: ThreadContent = {
|
const messageContent: ThreadContent = {
|
||||||
@ -123,7 +125,7 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
/**
|
/**
|
||||||
* Headers for the inference request
|
* Headers for the inference request
|
||||||
*/
|
*/
|
||||||
headers(): HeadersInit {
|
async headers(): Promise<HeadersInit> {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { OAIEngine } from './OAIEngine'
|
|||||||
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||||
*/
|
*/
|
||||||
export abstract class RemoteOAIEngine extends OAIEngine {
|
export abstract class RemoteOAIEngine extends OAIEngine {
|
||||||
// The inference engine
|
apiKey?: string
|
||||||
abstract apiKey: string
|
|
||||||
/**
|
/**
|
||||||
* On extension load, subscribe to events.
|
* On extension load, subscribe to events.
|
||||||
*/
|
*/
|
||||||
@ -17,10 +16,12 @@ export abstract class RemoteOAIEngine extends OAIEngine {
|
|||||||
/**
|
/**
|
||||||
* Headers for the inference request
|
* Headers for the inference request
|
||||||
*/
|
*/
|
||||||
override headers(): HeadersInit {
|
override async headers(): Promise<HeadersInit> {
|
||||||
return {
|
return {
|
||||||
|
...(this.apiKey && {
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
'Authorization': `Bearer ${this.apiKey}`,
|
||||||
'api-key': `${this.apiKey}`,
|
'api-key': `${this.apiKey}`,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,19 +5,16 @@ export type Handler = (route: string, args: any) => any
|
|||||||
|
|
||||||
export class RequestHandler {
|
export class RequestHandler {
|
||||||
handler: Handler
|
handler: Handler
|
||||||
adataper: RequestAdapter
|
adapter: RequestAdapter
|
||||||
|
|
||||||
constructor(handler: Handler, observer?: Function) {
|
constructor(handler: Handler, observer?: Function) {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
this.adataper = new RequestAdapter(observer)
|
this.adapter = new RequestAdapter(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
handle() {
|
handle() {
|
||||||
CoreRoutes.map((route) => {
|
CoreRoutes.map((route) => {
|
||||||
this.handler(route, async (...args: any[]) => {
|
this.handler(route, async (...args: any[]) => this.adapter.process(route, ...args))
|
||||||
const values = await this.adataper.process(route, ...args)
|
|
||||||
return values
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -316,6 +316,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestedModel = matchedModels[0]
|
const requestedModel = matchedModels[0]
|
||||||
|
|
||||||
const engineConfiguration = await getEngineConfiguration(requestedModel.engine)
|
const engineConfiguration = await getEngineConfiguration(requestedModel.engine)
|
||||||
|
|
||||||
let apiKey: string | undefined = undefined
|
let apiKey: string | undefined = undefined
|
||||||
@ -323,7 +324,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
|
|
||||||
if (engineConfiguration) {
|
if (engineConfiguration) {
|
||||||
apiKey = engineConfiguration.api_key
|
apiKey = engineConfiguration.api_key
|
||||||
apiUrl = engineConfiguration.full_url
|
apiUrl = engineConfiguration.full_url ?? DEFAULT_CHAT_COMPLETION_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: Record<string, any> = {
|
const headers: Record<string, any> = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AppConfiguration } from '../../types'
|
import { AppConfiguration, SettingComponentProps } from '../../types'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@ -125,14 +125,30 @@ const exec = async (command: string): Promise<string> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a hacky way to get the api key. we should comes up with a better
|
||||||
|
// way to handle this
|
||||||
export const getEngineConfiguration = async (engineId: string) => {
|
export const getEngineConfiguration = async (engineId: string) => {
|
||||||
if (engineId !== 'openai' && engineId !== 'groq') {
|
if (engineId !== 'openai' && engineId !== 'groq') return undefined
|
||||||
return undefined
|
|
||||||
|
const settingDirectoryPath = join(
|
||||||
|
getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
engineId === 'openai' ? 'inference-openai-extension' : 'inference-groq-extension',
|
||||||
|
'settings.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
const content = fs.readFileSync(settingDirectoryPath, 'utf-8')
|
||||||
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
|
const apiKeyId = engineId === 'openai' ? 'openai-api-key' : 'groq-api-key'
|
||||||
|
const keySetting = settings.find((setting) => setting.key === apiKeyId)
|
||||||
|
|
||||||
|
let apiKey = keySetting?.controllerProps.value
|
||||||
|
if (typeof apiKey !== 'string') apiKey = ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
api_key: apiKey,
|
||||||
|
full_url: undefined,
|
||||||
}
|
}
|
||||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
|
||||||
const filePath = join(directoryPath, `${engineId}.json`)
|
|
||||||
const data = fs.readFileSync(filePath, 'utf-8')
|
|
||||||
return JSON.parse(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,3 +9,4 @@ export * from './config'
|
|||||||
export * from './huggingface'
|
export * from './huggingface'
|
||||||
export * from './miscellaneous'
|
export * from './miscellaneous'
|
||||||
export * from './api'
|
export * from './api'
|
||||||
|
export * from './setting'
|
||||||
|
|||||||
1
core/src/types/setting/index.ts
Normal file
1
core/src/types/setting/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './settingComponent'
|
||||||
34
core/src/types/setting/settingComponent.ts
Normal file
34
core/src/types/setting/settingComponent.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export type SettingComponentProps = {
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
controllerType: ControllerType
|
||||||
|
controllerProps: SliderComponentProps | CheckboxComponentProps | InputComponentProps
|
||||||
|
|
||||||
|
extensionName?: string
|
||||||
|
requireModelReload?: boolean
|
||||||
|
configType?: ConfigType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfigType = 'runtime' | 'setting'
|
||||||
|
|
||||||
|
export type ControllerType = 'slider' | 'checkbox' | 'input'
|
||||||
|
|
||||||
|
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
||||||
|
|
||||||
|
export type InputComponentProps = {
|
||||||
|
placeholder: string
|
||||||
|
value: string
|
||||||
|
type?: InputType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SliderComponentProps = {
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
|
step: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxComponentProps = {
|
||||||
|
value: boolean
|
||||||
|
}
|
||||||
@ -7,12 +7,10 @@ import replace from '@rollup/plugin-replace'
|
|||||||
|
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
const pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: `src/index.ts`,
|
input: `src/index.ts`,
|
||||||
output: [{ file: pkg.main, format: 'es', sourcemap: true }],
|
output: [{ file: packageJson.main, format: 'es', sourcemap: true }],
|
||||||
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
||||||
external: [],
|
external: [],
|
||||||
watch: {
|
watch: {
|
||||||
@ -36,7 +34,7 @@ export default [
|
|||||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||||
resolve({
|
resolve({
|
||||||
extensions: ['.js', '.ts', '.svelte'],
|
extensions: ['.js', '.ts', '.svelte'],
|
||||||
browser: true
|
browser: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Resolve source maps to the original source
|
// Resolve source maps to the original source
|
||||||
|
|||||||
@ -1,13 +1,36 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
import { SettingComponentProps, getJanDataFolderPath } from '@janhq/core/node'
|
||||||
|
|
||||||
// Sec: Do not send engine settings over requests
|
// Sec: Do not send engine settings over requests
|
||||||
// Read it manually instead
|
// Read it manually instead
|
||||||
export const readEmbeddingEngine = (engineName: string) => {
|
export const readEmbeddingEngine = (engineName: string) => {
|
||||||
|
if (engineName !== 'openai' && engineName !== 'groq') {
|
||||||
const engineSettings = fs.readFileSync(
|
const engineSettings = fs.readFileSync(
|
||||||
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
return JSON.parse(engineSettings)
|
return JSON.parse(engineSettings)
|
||||||
|
} else {
|
||||||
|
const settingDirectoryPath = path.join(
|
||||||
|
getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
engineName === 'openai'
|
||||||
|
? 'inference-openai-extension'
|
||||||
|
: 'inference-groq-extension',
|
||||||
|
'settings.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
const content = fs.readFileSync(settingDirectoryPath, 'utf-8')
|
||||||
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
|
const apiKeyId = engineName === 'openai' ? 'openai-api-key' : 'groq-api-key'
|
||||||
|
const keySetting = settings.find((setting) => setting.key === apiKeyId)
|
||||||
|
|
||||||
|
let apiKey = keySetting?.controllerProps.value
|
||||||
|
if (typeof apiKey !== 'string') apiKey = ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
api_key: apiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
extensions/inference-groq-extension/resources/settings.json
Normal file
23
extensions/inference-groq-extension/resources/settings.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "chat-completions-endpoint",
|
||||||
|
"title": "Chat Completions Endpoint",
|
||||||
|
"description": "The endpoint to use for chat completions. See the [Groq Documentation](https://console.groq.com/docs/openai) for more information.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "Chat Completions Endpoint",
|
||||||
|
"value": "https://api.groq.com/openai/v1/chat/completions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "groq-api-key",
|
||||||
|
"title": "API Key",
|
||||||
|
"description": "The Groq API uses API keys for authentication. Visit your [API Keys](https://console.groq.com/keys) page to retrieve the API key you'll use in your requests.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "API Key",
|
||||||
|
"value": "",
|
||||||
|
"type": "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -6,78 +6,41 @@
|
|||||||
* @module inference-groq-extension/src/index
|
* @module inference-groq-extension/src/index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { RemoteOAIEngine } from '@janhq/core'
|
||||||
events,
|
|
||||||
fs,
|
|
||||||
AppConfigurationEventName,
|
|
||||||
joinPath,
|
|
||||||
RemoteOAIEngine,
|
|
||||||
} from '@janhq/core'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
declare const COMPLETION_URL: string
|
declare const SETTINGS: Array<any>
|
||||||
|
enum Settings {
|
||||||
|
apiKey = 'groq-api-key',
|
||||||
|
chatCompletionsEndPoint = 'chat-completions-endpoint',
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
*/
|
*/
|
||||||
export default class JanInferenceGroqExtension extends RemoteOAIEngine {
|
export default class JanInferenceGroqExtension extends RemoteOAIEngine {
|
||||||
private readonly _engineDir = 'file://engines'
|
inferenceUrl: string = ''
|
||||||
private readonly _engineMetadataFileName = 'groq.json'
|
|
||||||
|
|
||||||
inferenceUrl: string = COMPLETION_URL
|
|
||||||
provider = 'groq'
|
provider = 'groq'
|
||||||
apiKey = ''
|
|
||||||
|
|
||||||
private _engineSettings = {
|
override async onLoad(): Promise<void> {
|
||||||
full_url: COMPLETION_URL,
|
|
||||||
api_key: 'gsk-<your key here>',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to events emitted by the @janhq/core package.
|
|
||||||
*/
|
|
||||||
async onLoad() {
|
|
||||||
super.onLoad()
|
super.onLoad()
|
||||||
|
|
||||||
if (!(await fs.existsSync(this._engineDir))) {
|
// Register Settings
|
||||||
await fs.mkdir(this._engineDir)
|
this.registerSettings(SETTINGS)
|
||||||
}
|
|
||||||
|
|
||||||
this.writeDefaultEngineSettings()
|
// Retrieve API Key Setting
|
||||||
|
this.apiKey = await this.getSetting<string>(Settings.apiKey, '')
|
||||||
const settingsFilePath = await joinPath([
|
this.inferenceUrl = await this.getSetting<string>(
|
||||||
this._engineDir,
|
Settings.chatCompletionsEndPoint,
|
||||||
this._engineMetadataFileName,
|
''
|
||||||
])
|
|
||||||
|
|
||||||
// Events subscription
|
|
||||||
events.on(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
(settingsKey: string) => {
|
|
||||||
// Update settings on changes
|
|
||||||
if (settingsKey === settingsFilePath) this.writeDefaultEngineSettings()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeDefaultEngineSettings() {
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
try {
|
if (key === Settings.apiKey) {
|
||||||
const engineFile = join(this._engineDir, this._engineMetadataFileName)
|
this.apiKey = value as string
|
||||||
if (await fs.existsSync(engineFile)) {
|
} else if (key === Settings.chatCompletionsEndPoint) {
|
||||||
const engine = await fs.readFileSync(engineFile, 'utf-8')
|
this.inferenceUrl = value as string
|
||||||
this._engineSettings =
|
|
||||||
typeof engine === 'object' ? engine : JSON.parse(engine)
|
|
||||||
this.inferenceUrl = this._engineSettings.full_url
|
|
||||||
this.apiKey = this._engineSettings.api_key
|
|
||||||
} else {
|
|
||||||
await fs.writeFileSync(
|
|
||||||
engineFile,
|
|
||||||
JSON.stringify(this._engineSettings, null, 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
@ -17,8 +18,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
COMPLETION_URL: JSON.stringify('https://api.groq.com/openai/v1/chat/completions'),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"title": "Test",
|
||||||
|
"description": "Test",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "Test",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "embedding",
|
||||||
|
"title": "Embedding",
|
||||||
|
"description": "Whether to enable embedding.",
|
||||||
|
"controllerType": "checkbox",
|
||||||
|
"controllerProps": {
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ctx_len",
|
||||||
|
"title": "Context Length",
|
||||||
|
"description": "The context length for model operations varies; the maximum depends on the specific model used.",
|
||||||
|
"controllerType": "slider",
|
||||||
|
"controllerProps": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 4096,
|
||||||
|
"step": 128,
|
||||||
|
"value": 4096
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -5,13 +5,12 @@ import typescript from 'rollup-plugin-typescript2'
|
|||||||
import json from '@rollup/plugin-json'
|
import json from '@rollup/plugin-json'
|
||||||
import replace from '@rollup/plugin-replace'
|
import replace from '@rollup/plugin-replace'
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
const defaultSettingJson = require('./resources/default_settings.json')
|
||||||
const pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: `src/index.ts`,
|
input: `src/index.ts`,
|
||||||
output: [{ file: pkg.main, format: 'es', sourcemap: true }],
|
output: [{ file: packageJson.main, format: 'es', sourcemap: true }],
|
||||||
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
||||||
external: [],
|
external: [],
|
||||||
watch: {
|
watch: {
|
||||||
@ -19,7 +18,9 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({
|
replace({
|
||||||
|
EXTENSION_NAME: JSON.stringify(packageJson.name),
|
||||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||||
|
DEFAULT_SETTINGS: JSON.stringify(defaultSettingJson),
|
||||||
INFERENCE_URL: JSON.stringify(
|
INFERENCE_URL: JSON.stringify(
|
||||||
process.env.INFERENCE_URL ||
|
process.env.INFERENCE_URL ||
|
||||||
'http://127.0.0.1:3928/inferences/llamacpp/chat_completion'
|
'http://127.0.0.1:3928/inferences/llamacpp/chat_completion'
|
||||||
|
|||||||
@ -2,6 +2,8 @@ declare const NODE: string
|
|||||||
declare const INFERENCE_URL: string
|
declare const INFERENCE_URL: string
|
||||||
declare const TROUBLESHOOTING_URL: string
|
declare const TROUBLESHOOTING_URL: string
|
||||||
declare const JAN_SERVER_INFERENCE_URL: string
|
declare const JAN_SERVER_INFERENCE_URL: string
|
||||||
|
declare const EXTENSION_NAME: string
|
||||||
|
declare const DEFAULT_SETTINGS: Array<any>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response from the initModel function.
|
* The response from the initModel function.
|
||||||
|
|||||||
@ -58,8 +58,6 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine {
|
|||||||
this.inferenceUrl = `${window.core?.api?.baseApiUrl}/v1/chat/completions`
|
this.inferenceUrl = `${window.core?.api?.baseApiUrl}/v1/chat/completions`
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Inference url: ', this.inferenceUrl)
|
|
||||||
|
|
||||||
this.getNitroProcesHealthIntervalId = setInterval(
|
this.getNitroProcesHealthIntervalId = setInterval(
|
||||||
() => this.periodicallyGetNitroHealth(),
|
() => this.periodicallyGetNitroHealth(),
|
||||||
JanInferenceNitroExtension._intervalHealthCheck
|
JanInferenceNitroExtension._intervalHealthCheck
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "chat-completions-endpoint",
|
||||||
|
"title": "Chat Completions Endpoint",
|
||||||
|
"description": "The endpoint to use for chat completions. See the [OpenAI API documentation](https://platform.openai.com/docs/api-reference/chat/create) for more information.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "Chat Completions Endpoint",
|
||||||
|
"value": "https://api.openai.com/v1/chat/completions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "openai-api-key",
|
||||||
|
"title": "API Key",
|
||||||
|
"description": "The OpenAI API uses API keys for authentication. Visit your [API Keys](https://platform.openai.com/account/api-keys) page to retrieve the API key you'll use in your requests.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "API Key",
|
||||||
|
"value": "",
|
||||||
|
"type": "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -5,85 +5,41 @@
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @module inference-openai-extension/src/index
|
* @module inference-openai-extension/src/index
|
||||||
*/
|
*/
|
||||||
declare const ENGINE: string
|
|
||||||
|
|
||||||
import {
|
import { RemoteOAIEngine } from '@janhq/core'
|
||||||
events,
|
|
||||||
fs,
|
|
||||||
AppConfigurationEventName,
|
|
||||||
joinPath,
|
|
||||||
RemoteOAIEngine,
|
|
||||||
} from '@janhq/core'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
declare const COMPLETION_URL: string
|
|
||||||
|
|
||||||
|
declare const SETTINGS: Array<any>
|
||||||
|
enum Settings {
|
||||||
|
apiKey = 'openai-api-key',
|
||||||
|
chatCompletionsEndPoint = 'chat-completions-endpoint',
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
*/
|
*/
|
||||||
export default class JanInferenceOpenAIExtension extends RemoteOAIEngine {
|
export default class JanInferenceOpenAIExtension extends RemoteOAIEngine {
|
||||||
private static readonly _engineDir = 'file://engines'
|
inferenceUrl: string = ''
|
||||||
private static readonly _engineMetadataFileName = `${ENGINE}.json`
|
|
||||||
|
|
||||||
private _engineSettings = {
|
|
||||||
full_url: COMPLETION_URL,
|
|
||||||
api_key: 'sk-<your key here>',
|
|
||||||
}
|
|
||||||
|
|
||||||
inferenceUrl: string = COMPLETION_URL
|
|
||||||
provider: string = 'openai'
|
provider: string = 'openai'
|
||||||
apiKey: string = ''
|
|
||||||
|
|
||||||
// TODO: Just use registerSettings from BaseExtension
|
override async onLoad(): Promise<void> {
|
||||||
// Remove these methods
|
|
||||||
/**
|
|
||||||
* Subscribes to events emitted by the @janhq/core package.
|
|
||||||
*/
|
|
||||||
async onLoad() {
|
|
||||||
super.onLoad()
|
super.onLoad()
|
||||||
|
|
||||||
if (!(await fs.existsSync(JanInferenceOpenAIExtension._engineDir))) {
|
// Register Settings
|
||||||
await fs.mkdir(JanInferenceOpenAIExtension._engineDir)
|
this.registerSettings(SETTINGS)
|
||||||
}
|
|
||||||
|
|
||||||
this.writeDefaultEngineSettings()
|
this.apiKey = await this.getSetting<string>(Settings.apiKey, '')
|
||||||
|
this.inferenceUrl = await this.getSetting<string>(
|
||||||
const settingsFilePath = await joinPath([
|
Settings.chatCompletionsEndPoint,
|
||||||
JanInferenceOpenAIExtension._engineDir,
|
''
|
||||||
JanInferenceOpenAIExtension._engineMetadataFileName,
|
|
||||||
])
|
|
||||||
|
|
||||||
events.on(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
(settingsKey: string) => {
|
|
||||||
// Update settings on changes
|
|
||||||
if (settingsKey === settingsFilePath) this.writeDefaultEngineSettings()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeDefaultEngineSettings() {
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
try {
|
if (key === Settings.apiKey) {
|
||||||
const engineFile = join(
|
this.apiKey = value as string
|
||||||
JanInferenceOpenAIExtension._engineDir,
|
} else if (key === Settings.chatCompletionsEndPoint) {
|
||||||
JanInferenceOpenAIExtension._engineMetadataFileName
|
this.inferenceUrl = value as string
|
||||||
)
|
|
||||||
if (await fs.existsSync(engineFile)) {
|
|
||||||
const engine = await fs.readFileSync(engineFile, 'utf-8')
|
|
||||||
this._engineSettings =
|
|
||||||
typeof engine === 'object' ? engine : JSON.parse(engine)
|
|
||||||
this.inferenceUrl = this._engineSettings.full_url
|
|
||||||
this.apiKey = this._engineSettings.api_key
|
|
||||||
} else {
|
|
||||||
await fs.writeFileSync(
|
|
||||||
engineFile,
|
|
||||||
JSON.stringify(this._engineSettings, null, 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
@ -17,8 +18,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
ENGINE: JSON.stringify(packageJson.engine),
|
ENGINE: JSON.stringify(packageJson.engine),
|
||||||
COMPLETION_URL: JSON.stringify('https://api.openai.com/v1/chat/completions'),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "chat-completions-endpoint",
|
||||||
|
"title": "Chat Completions Endpoint",
|
||||||
|
"description": "The endpoint to use for chat completions.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "Chat Completions Endpoint",
|
||||||
|
"value": "http://localhost:8000/v2/models/tensorrt_llm_bls/generate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tritonllm-api-key",
|
||||||
|
"title": "Triton LLM API Key",
|
||||||
|
"description": "The Triton LLM API uses API keys for authentication.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "API Key",
|
||||||
|
"value": "",
|
||||||
|
"type": "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -6,77 +6,44 @@
|
|||||||
* @module inference-nvidia-triton-trt-llm-extension/src/index
|
* @module inference-nvidia-triton-trt-llm-extension/src/index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { RemoteOAIEngine } from '@janhq/core'
|
||||||
AppConfigurationEventName,
|
|
||||||
events,
|
|
||||||
fs,
|
|
||||||
joinPath,
|
|
||||||
Model,
|
|
||||||
RemoteOAIEngine,
|
|
||||||
} from '@janhq/core'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
|
declare const SETTINGS: Array<any>
|
||||||
|
enum Settings {
|
||||||
|
apiKey = 'tritonllm-api-key',
|
||||||
|
chatCompletionsEndPoint = 'chat-completions-endpoint',
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
*/
|
*/
|
||||||
export default class JanInferenceTritonTrtLLMExtension extends RemoteOAIEngine {
|
export default class JanInferenceTritonTrtLLMExtension extends RemoteOAIEngine {
|
||||||
private readonly _engineDir = 'file://engines'
|
|
||||||
private readonly _engineMetadataFileName = 'triton_trtllm.json'
|
|
||||||
|
|
||||||
inferenceUrl: string = ''
|
inferenceUrl: string = ''
|
||||||
provider: string = 'triton_trtllm'
|
provider: string = 'triton_trtllm'
|
||||||
apiKey: string = ''
|
|
||||||
|
|
||||||
_engineSettings: {
|
|
||||||
base_url: ''
|
|
||||||
api_key: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to events emitted by the @janhq/core package.
|
* Subscribes to events emitted by the @janhq/core package.
|
||||||
*/
|
*/
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
super.onLoad()
|
super.onLoad()
|
||||||
if (!(await fs.existsSync(this._engineDir))) {
|
|
||||||
await fs.mkdir(this._engineDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.writeDefaultEngineSettings()
|
// Register Settings
|
||||||
|
this.registerSettings(SETTINGS)
|
||||||
|
|
||||||
const settingsFilePath = await joinPath([
|
// Retrieve API Key Setting
|
||||||
this._engineDir,
|
this.apiKey = await this.getSetting<string>(Settings.apiKey, '')
|
||||||
this._engineMetadataFileName,
|
this.inferenceUrl = await this.getSetting<string>(
|
||||||
])
|
Settings.chatCompletionsEndPoint,
|
||||||
|
''
|
||||||
// Events subscription
|
|
||||||
events.on(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
(settingsKey: string) => {
|
|
||||||
// Update settings on changes
|
|
||||||
if (settingsKey === settingsFilePath) this.writeDefaultEngineSettings()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeDefaultEngineSettings() {
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
try {
|
if (key === Settings.apiKey) {
|
||||||
const engine_json = join(this._engineDir, this._engineMetadataFileName)
|
this.apiKey = value as string
|
||||||
if (await fs.existsSync(engine_json)) {
|
} else if (key === Settings.chatCompletionsEndPoint) {
|
||||||
const engine = await fs.readFileSync(engine_json, 'utf-8')
|
this.inferenceUrl = value as string
|
||||||
this._engineSettings =
|
|
||||||
typeof engine === 'object' ? engine : JSON.parse(engine)
|
|
||||||
this.inferenceUrl = this._engineSettings.base_url
|
|
||||||
this.apiKey = this._engineSettings.api_key
|
|
||||||
} else {
|
|
||||||
await fs.writeFileSync(
|
|
||||||
engine_json,
|
|
||||||
JSON.stringify(this._engineSettings, null, 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
@ -17,6 +18,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import resolve from '@rollup/plugin-node-resolve'
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
import commonjs from '@rollup/plugin-commonjs'
|
|
||||||
import sourceMaps from 'rollup-plugin-sourcemaps'
|
import sourceMaps from 'rollup-plugin-sourcemaps'
|
||||||
import typescript from 'rollup-plugin-typescript2'
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
import json from '@rollup/plugin-json'
|
import json from '@rollup/plugin-json'
|
||||||
@ -7,12 +6,10 @@ import replace from '@rollup/plugin-replace'
|
|||||||
|
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
const pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: `src/index.ts`,
|
input: `src/index.ts`,
|
||||||
output: [{ file: pkg.main, format: 'es', sourcemap: true }],
|
output: [{ file: packageJson.main, format: 'es', sourcemap: true }],
|
||||||
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
||||||
external: [],
|
external: [],
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@ -251,7 +251,7 @@ export default class TensorRTLLMExtension extends LocalOAIEngine {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
override inference(data: MessageRequest): void {
|
override async inference(data: MessageRequest) {
|
||||||
if (!this.loadedModel) return
|
if (!this.loadedModel) return
|
||||||
// TensorRT LLM Extension supports streaming only
|
// TensorRT LLM Extension supports streaming only
|
||||||
if (data.model) data.model.parameters.stream = true
|
if (data.model) data.model.parameters.stream = true
|
||||||
|
|||||||
@ -38,7 +38,6 @@ import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
|||||||
import { toGibibytes } from '@/utils/converter'
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
import ModelLabel from '../ModelLabel'
|
import ModelLabel from '../ModelLabel'
|
||||||
import OpenAiKeyInput from '../OpenAiKeyInput'
|
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
@ -144,7 +143,7 @@ const DropdownListSidebar = ({
|
|||||||
|
|
||||||
// Update model parameter to the thread file
|
// Update model parameter to the thread file
|
||||||
if (model)
|
if (model)
|
||||||
updateModelParameter(activeThread.id, {
|
updateModelParameter(activeThread, {
|
||||||
params: modelParams,
|
params: modelParams,
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
engine: model.engine,
|
engine: model.engine,
|
||||||
@ -170,7 +169,6 @@ const DropdownListSidebar = ({
|
|||||||
stateModel.model === selectedModel?.id && stateModel.loading
|
stateModel.model === selectedModel?.id && stateModel.loading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative w-full overflow-hidden rounded-md',
|
'relative w-full overflow-hidden rounded-md',
|
||||||
@ -271,10 +269,7 @@ const DropdownListSidebar = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative flex w-full justify-between">
|
<div className="relative flex w-full justify-between">
|
||||||
<div>
|
<span className="line-clamp-1 block">{x.name}</span>
|
||||||
<span className="line-clamp-1 block">
|
|
||||||
{x.name}
|
|
||||||
</span>
|
|
||||||
<div className="absolute right-0 top-2 space-x-2">
|
<div className="absolute right-0 top-2 space-x-2">
|
||||||
<span className="font-bold text-muted-foreground">
|
<span className="font-bold text-muted-foreground">
|
||||||
{toGibibytes(x.metadata.size)}
|
{toGibibytes(x.metadata.size)}
|
||||||
@ -284,7 +279,6 @@ const DropdownListSidebar = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@ -333,9 +327,6 @@ const DropdownListSidebar = ({
|
|||||||
</SelectPortal>
|
</SelectPortal>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OpenAiKeyInput />
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,7 @@ const ModelConfigInput: React.FC<Props> = ({
|
|||||||
description,
|
description,
|
||||||
placeholder,
|
placeholder,
|
||||||
onValueChanged,
|
onValueChanged,
|
||||||
}) => {
|
}) => (
|
||||||
return (
|
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-2 flex items-center gap-x-2">
|
<div className="mb-2 flex items-center gap-x-2">
|
||||||
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||||
@ -52,7 +51,6 @@ const ModelConfigInput: React.FC<Props> = ({
|
|||||||
disabled={!enabled}
|
disabled={!enabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
export default ModelConfigInput
|
export default ModelConfigInput
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
import { InferenceEngine } from '@janhq/core'
|
|
||||||
import { Input } from '@janhq/uikit'
|
|
||||||
|
|
||||||
import { useAtomValue } from 'jotai'
|
|
||||||
|
|
||||||
import { useEngineSettings } from '@/hooks/useEngineSettings'
|
|
||||||
|
|
||||||
import { selectedModelAtom } from '../DropdownListSidebar'
|
|
||||||
|
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
|
||||||
|
|
||||||
const OpenAiKeyInput: React.FC = () => {
|
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
|
||||||
const serverEnabled = useAtomValue(serverEnabledAtom)
|
|
||||||
const [openAISettings, setOpenAISettings] = useState<
|
|
||||||
{ api_key: string } | undefined
|
|
||||||
>(undefined)
|
|
||||||
const { readOpenAISettings, saveOpenAISettings } = useEngineSettings()
|
|
||||||
|
|
||||||
const [groqSettings, setGroqSettings] = useState<
|
|
||||||
{ api_key: string } | undefined
|
|
||||||
>(undefined)
|
|
||||||
const { readGroqSettings, saveGroqSettings } = useEngineSettings()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
readOpenAISettings().then((settings) => {
|
|
||||||
setOpenAISettings(settings)
|
|
||||||
})
|
|
||||||
}, [readOpenAISettings])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
readGroqSettings().then((settings) => {
|
|
||||||
setGroqSettings(settings)
|
|
||||||
})
|
|
||||||
}, [readGroqSettings])
|
|
||||||
|
|
||||||
if (
|
|
||||||
!selectedModel ||
|
|
||||||
(selectedModel.engine !== InferenceEngine.openai &&
|
|
||||||
selectedModel.engine !== InferenceEngine.groq)
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCurrentApiKey = () => {
|
|
||||||
if (selectedModel.engine === InferenceEngine.openai) {
|
|
||||||
return openAISettings?.api_key
|
|
||||||
} else if (selectedModel.engine === InferenceEngine.groq) {
|
|
||||||
return groqSettings?.api_key
|
|
||||||
}
|
|
||||||
return '' // Default return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleApiKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const newApiKey = e.target.value
|
|
||||||
if (selectedModel.engine === InferenceEngine.openai) {
|
|
||||||
saveOpenAISettings({ apiKey: newApiKey })
|
|
||||||
} else if (selectedModel.engine === InferenceEngine.groq) {
|
|
||||||
saveGroqSettings({ apiKey: newApiKey })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="my-4">
|
|
||||||
<label
|
|
||||||
id="thread-title"
|
|
||||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
API Key
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
disabled={serverEnabled}
|
|
||||||
id="assistant-instructions"
|
|
||||||
placeholder={getCurrentApiKey()}
|
|
||||||
defaultValue={getCurrentApiKey()}
|
|
||||||
onChange={handleApiKeyChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OpenAiKeyInput
|
|
||||||
@ -10,11 +10,14 @@ import useGetSystemResources from '@/hooks/useGetSystemResources'
|
|||||||
import useModels from '@/hooks/useModels'
|
import useModels from '@/hooks/useModels'
|
||||||
import useThreads from '@/hooks/useThreads'
|
import useThreads from '@/hooks/useThreads'
|
||||||
|
|
||||||
|
import { SettingScreenList } from '@/screens/Settings'
|
||||||
|
|
||||||
import { defaultJanDataFolderAtom } from '@/helpers/atoms/App.atom'
|
import { defaultJanDataFolderAtom } from '@/helpers/atoms/App.atom'
|
||||||
import {
|
import {
|
||||||
janDataFolderPathAtom,
|
janDataFolderPathAtom,
|
||||||
quickAskEnabledAtom,
|
quickAskEnabledAtom,
|
||||||
} from '@/helpers/atoms/AppConfig.atom'
|
} from '@/helpers/atoms/AppConfig.atom'
|
||||||
|
import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@ -24,6 +27,7 @@ const DataLoader: React.FC<Props> = ({ children }) => {
|
|||||||
const setJanDataFolderPath = useSetAtom(janDataFolderPathAtom)
|
const setJanDataFolderPath = useSetAtom(janDataFolderPathAtom)
|
||||||
const setQuickAskEnabled = useSetAtom(quickAskEnabledAtom)
|
const setQuickAskEnabled = useSetAtom(quickAskEnabledAtom)
|
||||||
const setJanDefaultDataFolder = useSetAtom(defaultJanDataFolderAtom)
|
const setJanDefaultDataFolder = useSetAtom(defaultJanDataFolderAtom)
|
||||||
|
const setJanSettingScreen = useSetAtom(janSettingScreenAtom)
|
||||||
|
|
||||||
useModels()
|
useModels()
|
||||||
useThreads()
|
useThreads()
|
||||||
@ -49,6 +53,13 @@ const DataLoader: React.FC<Props> = ({ children }) => {
|
|||||||
getDefaultJanDataFolder()
|
getDefaultJanDataFolder()
|
||||||
}, [setJanDefaultDataFolder])
|
}, [setJanDefaultDataFolder])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const janSettingScreen = SettingScreenList.filter(
|
||||||
|
(screen) => window.electronAPI || screen !== 'Extensions'
|
||||||
|
)
|
||||||
|
setJanSettingScreen(janSettingScreen)
|
||||||
|
}, [setJanSettingScreen])
|
||||||
|
|
||||||
console.debug('Load Data...')
|
console.debug('Load Data...')
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment>{children}</Fragment>
|
||||||
|
|||||||
@ -119,7 +119,13 @@ export class ExtensionManager {
|
|||||||
) {
|
) {
|
||||||
this.register(
|
this.register(
|
||||||
extension.name ?? extension.url,
|
extension.name ?? extension.url,
|
||||||
new extensionClass.default()
|
new extensionClass.default(
|
||||||
|
extension.url,
|
||||||
|
extension.name,
|
||||||
|
extension.active,
|
||||||
|
extension.description,
|
||||||
|
extension.version
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
web/helpers/atoms/Setting.atom.ts
Normal file
7
web/helpers/atoms/Setting.atom.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { atom } from 'jotai'
|
||||||
|
|
||||||
|
import { SettingScreen } from '@/screens/Settings'
|
||||||
|
|
||||||
|
export const selectedSettingAtom = atom<SettingScreen | string>('My Models')
|
||||||
|
|
||||||
|
export const janSettingScreenAtom = atom<SettingScreen[]>([])
|
||||||
@ -127,13 +127,6 @@ export const setThreadModelParamsAtom = atom(
|
|||||||
(get, set, threadId: string, params: ModelParams) => {
|
(get, set, threadId: string, params: ModelParams) => {
|
||||||
const currentState = { ...get(threadModelParamsAtom) }
|
const currentState = { ...get(threadModelParamsAtom) }
|
||||||
currentState[threadId] = params
|
currentState[threadId] = params
|
||||||
console.debug(
|
|
||||||
`Update model params for thread ${threadId}, ${JSON.stringify(
|
|
||||||
params,
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
set(threadModelParamsAtom, currentState)
|
set(threadModelParamsAtom, currentState)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -115,7 +115,8 @@ export function useActiveModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stopModel = useCallback(async () => {
|
const stopModel = useCallback(async () => {
|
||||||
if (activeModel) {
|
if (!activeModel) return
|
||||||
|
|
||||||
setStateModel({ state: 'stop', loading: true, model: activeModel.id })
|
setStateModel({ state: 'stop', loading: true, model: activeModel.id })
|
||||||
const engine = EngineManager.instance().get(activeModel.engine)
|
const engine = EngineManager.instance().get(activeModel.engine)
|
||||||
await engine
|
await engine
|
||||||
@ -125,7 +126,6 @@ export function useActiveModel() {
|
|||||||
setActiveModel(undefined)
|
setActiveModel(undefined)
|
||||||
setStateModel({ state: 'start', loading: false, model: '' })
|
setStateModel({ state: 'start', loading: false, model: '' })
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}, [activeModel, setActiveModel, setStateModel])
|
}, [activeModel, setActiveModel, setStateModel])
|
||||||
|
|
||||||
return { activeModel, startModel, stopModel, stateModel }
|
return { activeModel, startModel, stopModel, stateModel }
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Assistant,
|
Assistant,
|
||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
@ -134,13 +136,16 @@ export const useCreateNewThread = () => {
|
|||||||
setActiveThread(thread)
|
setActiveThread(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateThreadMetadata(thread: Thread) {
|
const updateThreadMetadata = useCallback(
|
||||||
|
async (thread: Thread) => {
|
||||||
updateThread(thread)
|
updateThread(thread)
|
||||||
|
|
||||||
await extensionManager
|
await extensionManager
|
||||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||||
?.saveThread(thread)
|
?.saveThread(thread)
|
||||||
}
|
},
|
||||||
|
[updateThread]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requestCreateNewThread,
|
requestCreateNewThread,
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
import { useCallback } from 'react'
|
|
||||||
|
|
||||||
import { fs, joinPath, events, AppConfigurationEventName } from '@janhq/core'
|
|
||||||
|
|
||||||
export const useEngineSettings = () => {
|
|
||||||
const readOpenAISettings = useCallback(async () => {
|
|
||||||
if (
|
|
||||||
!(await fs.existsSync(await joinPath(['file://engines', 'openai.json'])))
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const settings = await fs.readFileSync(
|
|
||||||
await joinPath(['file://engines', 'openai.json']),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
if (settings) {
|
|
||||||
return typeof settings === 'object' ? settings : JSON.parse(settings)
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const saveOpenAISettings = async ({
|
|
||||||
apiKey,
|
|
||||||
}: {
|
|
||||||
apiKey: string | undefined
|
|
||||||
}) => {
|
|
||||||
const settings = await readOpenAISettings()
|
|
||||||
const settingFilePath = await joinPath(['file://engines', 'openai.json'])
|
|
||||||
|
|
||||||
settings.api_key = apiKey
|
|
||||||
|
|
||||||
await fs.writeFileSync(settingFilePath, JSON.stringify(settings))
|
|
||||||
|
|
||||||
// Sec: Don't attach the settings data to the event
|
|
||||||
events.emit(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
settingFilePath
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const readGroqSettings = useCallback(async () => {
|
|
||||||
if (!(await fs.existsSync(await joinPath(['file://engines', 'groq.json']))))
|
|
||||||
return {}
|
|
||||||
const settings = await fs.readFileSync(
|
|
||||||
await joinPath(['file://engines', 'groq.json']),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
if (settings) {
|
|
||||||
return typeof settings === 'object' ? settings : JSON.parse(settings)
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const saveGroqSettings = async ({
|
|
||||||
apiKey,
|
|
||||||
}: {
|
|
||||||
apiKey: string | undefined
|
|
||||||
}) => {
|
|
||||||
const settings = await readGroqSettings()
|
|
||||||
const settingFilePath = await joinPath(['file://engines', 'groq.json'])
|
|
||||||
|
|
||||||
settings.api_key = apiKey
|
|
||||||
|
|
||||||
await fs.writeFileSync(settingFilePath, JSON.stringify(settings))
|
|
||||||
|
|
||||||
// Sec: Don't attach the settings data to the event
|
|
||||||
events.emit(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
settingFilePath
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
readOpenAISettings,
|
|
||||||
saveOpenAISettings,
|
|
||||||
readGroqSettings,
|
|
||||||
saveGroqSettings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
ExtensionTypeEnum,
|
ExtensionTypeEnum,
|
||||||
@ -16,10 +17,8 @@ import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
|||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
import {
|
import {
|
||||||
ModelParams,
|
ModelParams,
|
||||||
activeThreadStateAtom,
|
|
||||||
getActiveThreadModelParamsAtom,
|
getActiveThreadModelParamsAtom,
|
||||||
setThreadModelParamsAtom,
|
setThreadModelParamsAtom,
|
||||||
threadsAtom,
|
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
export type UpdateModelParameter = {
|
export type UpdateModelParameter = {
|
||||||
@ -29,27 +28,12 @@ export type UpdateModelParameter = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function useUpdateModelParameters() {
|
export default function useUpdateModelParameters() {
|
||||||
const threads = useAtomValue(threadsAtom)
|
|
||||||
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
|
||||||
const activeThreadState = useAtomValue(activeThreadStateAtom)
|
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
|
const setThreadModelParams = useSetAtom(setThreadModelParamsAtom)
|
||||||
|
|
||||||
const updateModelParameter = async (
|
const updateModelParameter = useCallback(
|
||||||
threadId: string,
|
async (thread: Thread, settings: UpdateModelParameter) => {
|
||||||
settings: UpdateModelParameter
|
|
||||||
) => {
|
|
||||||
const thread = threads.find((thread) => thread.id === threadId)
|
|
||||||
if (!thread) {
|
|
||||||
console.error(`Thread ${threadId} not found`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeThreadState) {
|
|
||||||
console.error('No active thread')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = settings.modelId
|
const params = settings.modelId
|
||||||
? settings.params
|
? settings.params
|
||||||
: { ...activeModelParams, ...settings.params }
|
: { ...activeModelParams, ...settings.params }
|
||||||
@ -85,7 +69,9 @@ export default function useUpdateModelParameters() {
|
|||||||
await extensionManager
|
await extensionManager
|
||||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||||
?.saveThread(updatedThread)
|
?.saveThread(updatedThread)
|
||||||
}
|
},
|
||||||
|
[activeModelParams, selectedModel, setThreadModelParams]
|
||||||
|
)
|
||||||
|
|
||||||
return { updateModelParameter }
|
return { updateModelParameter }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,41 @@
|
|||||||
import { useAtomValue } from 'jotai'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { SettingComponentProps } from '@janhq/core'
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
|
|
||||||
import SettingComponentBuilder, {
|
import SettingComponentBuilder from '../ModelSetting/SettingComponent'
|
||||||
SettingComponentData,
|
|
||||||
} from '../ModelSetting/SettingComponent'
|
|
||||||
|
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import {
|
||||||
|
activeThreadAtom,
|
||||||
|
engineParamsUpdateAtom,
|
||||||
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const AssistantSetting = ({
|
type Props = {
|
||||||
componentData,
|
componentData: SettingComponentProps[]
|
||||||
}: {
|
}
|
||||||
componentData: SettingComponentData[]
|
|
||||||
}) => {
|
const AssistantSetting: React.FC<Props> = ({ componentData }) => {
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
|
const { stopModel } = useActiveModel()
|
||||||
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
|
||||||
|
const onValueChanged = useCallback(
|
||||||
|
(key: string, value: string | number | boolean) => {
|
||||||
|
if (!activeThread) return
|
||||||
|
const shouldReloadModel =
|
||||||
|
componentData.find((x) => x.key === key)?.requireModelReload ?? false
|
||||||
|
if (shouldReloadModel) {
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{activeThread && componentData && (
|
|
||||||
<SettingComponentBuilder
|
|
||||||
componentData={componentData}
|
|
||||||
updater={(_, name, value) => {
|
|
||||||
if (
|
if (
|
||||||
activeThread.assistants[0].tools &&
|
activeThread.assistants[0].tools &&
|
||||||
(name === 'chunk_overlap' || name === 'chunk_size')
|
(key === 'chunk_overlap' || key === 'chunk_size')
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
activeThread.assistants[0].tools[0]?.settings.chunk_size <
|
activeThread.assistants[0].tools[0]?.settings.chunk_size <
|
||||||
@ -33,22 +44,18 @@ const AssistantSetting = ({
|
|||||||
activeThread.assistants[0].tools[0].settings.chunk_overlap =
|
activeThread.assistants[0].tools[0].settings.chunk_overlap =
|
||||||
activeThread.assistants[0].tools[0].settings.chunk_size
|
activeThread.assistants[0].tools[0].settings.chunk_size
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
name === 'chunk_size' &&
|
key === 'chunk_size' &&
|
||||||
value <
|
value < activeThread.assistants[0].tools[0].settings.chunk_overlap
|
||||||
activeThread.assistants[0].tools[0].settings.chunk_overlap
|
|
||||||
) {
|
) {
|
||||||
activeThread.assistants[0].tools[0].settings.chunk_overlap =
|
activeThread.assistants[0].tools[0].settings.chunk_overlap = value
|
||||||
value
|
|
||||||
} else if (
|
} else if (
|
||||||
name === 'chunk_overlap' &&
|
key === 'chunk_overlap' &&
|
||||||
value > activeThread.assistants[0].tools[0].settings.chunk_size
|
value > activeThread.assistants[0].tools[0].settings.chunk_size
|
||||||
) {
|
) {
|
||||||
activeThread.assistants[0].tools[0].settings.chunk_size = value
|
activeThread.assistants[0].tools[0].settings.chunk_size = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateThreadMetadata({
|
updateThreadMetadata({
|
||||||
...activeThread,
|
...activeThread,
|
||||||
assistants: [
|
assistants: [
|
||||||
@ -61,17 +68,31 @@ const AssistantSetting = ({
|
|||||||
settings: {
|
settings: {
|
||||||
...(activeThread.assistants[0].tools &&
|
...(activeThread.assistants[0].tools &&
|
||||||
activeThread.assistants[0].tools[0]?.settings),
|
activeThread.assistants[0].tools[0]?.settings),
|
||||||
[name]: value,
|
[key]: value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}}
|
},
|
||||||
|
[
|
||||||
|
activeThread,
|
||||||
|
componentData,
|
||||||
|
setEngineParamsUpdate,
|
||||||
|
stopModel,
|
||||||
|
updateThreadMetadata,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!activeThread) return null
|
||||||
|
if (componentData.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingComponentBuilder
|
||||||
|
componentProps={componentData}
|
||||||
|
onValueUpdated={onValueChanged}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
web/screens/Chat/ChatBody/EmptyModel/index.tsx
Normal file
30
web/screens/Chat/ChatBody/EmptyModel/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Button } from '@janhq/uikit'
|
||||||
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
|
const EmptyModel: React.FC = () => {
|
||||||
|
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex h-full w-3/4 flex-col items-center justify-center text-center">
|
||||||
|
<LogoMark className="mx-auto mb-4 animate-wave" width={56} height={56} />
|
||||||
|
<h1 className="text-2xl font-bold">Welcome!</h1>
|
||||||
|
<p className="mt-1 text-base">You need to download your first model</p>
|
||||||
|
<Button
|
||||||
|
className="mt-4"
|
||||||
|
onClick={() => setMainViewState(MainViewState.Hub)}
|
||||||
|
>
|
||||||
|
Explore The Hub
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(EmptyModel)
|
||||||
46
web/screens/Chat/ChatBody/EmptyThread/index.tsx
Normal file
46
web/screens/Chat/ChatBody/EmptyThread/index.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { InferenceEngine } from '@janhq/core'
|
||||||
|
import { Button } from '@janhq/uikit'
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
|
const EmptyThread: React.FC = () => {
|
||||||
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
|
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||||
|
const showOnboardingStep =
|
||||||
|
downloadedModels.filter((e) => e.engine === InferenceEngine.nitro)
|
||||||
|
.length === 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex h-full w-3/4 flex-col items-center justify-center text-center">
|
||||||
|
<LogoMark className="mx-auto mb-4 animate-wave" width={56} height={56} />
|
||||||
|
{showOnboardingStep ? (
|
||||||
|
<>
|
||||||
|
<p className="mt-1 text-base font-medium">
|
||||||
|
{`You don't have a local model yet.`}
|
||||||
|
</p>
|
||||||
|
<div className="w-auto px-4 py-2">
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
className="bg-blue-100 font-bold text-blue-600 hover:bg-blue-100 hover:text-blue-600"
|
||||||
|
onClick={() => setMainViewState(MainViewState.Hub)}
|
||||||
|
>
|
||||||
|
Explore The Hub
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="mt-1 text-base font-medium">How can I help you?</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(EmptyThread)
|
||||||
@ -1,80 +1,26 @@
|
|||||||
import { Fragment } from 'react'
|
|
||||||
|
|
||||||
import ScrollToBottom from 'react-scroll-to-bottom'
|
import ScrollToBottom from 'react-scroll-to-bottom'
|
||||||
|
|
||||||
import { InferenceEngine, MessageStatus } from '@janhq/core'
|
import { MessageStatus } from '@janhq/core'
|
||||||
import { Button } from '@janhq/uikit'
|
import { useAtomValue } from 'jotai'
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
|
||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
|
||||||
|
|
||||||
import ChatItem from '../ChatItem'
|
import ChatItem from '../ChatItem'
|
||||||
|
|
||||||
import ErrorMessage from '../ErrorMessage'
|
import ErrorMessage from '../ErrorMessage'
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import EmptyModel from './EmptyModel'
|
||||||
|
import EmptyThread from './EmptyThread'
|
||||||
|
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const ChatBody: React.FC = () => {
|
const ChatBody: React.FC = () => {
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||||
const setMainViewState = useSetAtom(mainViewStateAtom)
|
|
||||||
|
|
||||||
if (downloadedModels.length === 0)
|
if (downloadedModels.length === 0) return <EmptyModel />
|
||||||
return (
|
if (messages.length === 0) return <EmptyThread />
|
||||||
<div className="mx-auto flex h-full w-3/4 flex-col items-center justify-center text-center">
|
|
||||||
<LogoMark
|
|
||||||
className="mx-auto mb-4 animate-wave"
|
|
||||||
width={56}
|
|
||||||
height={56}
|
|
||||||
/>
|
|
||||||
<h1 className="text-2xl font-bold">Welcome!</h1>
|
|
||||||
<p className="mt-1 text-base">You need to download your first model</p>
|
|
||||||
<Button
|
|
||||||
className="mt-4"
|
|
||||||
onClick={() => setMainViewState(MainViewState.Hub)}
|
|
||||||
>
|
|
||||||
Explore The Hub
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const showOnboardingStep =
|
|
||||||
downloadedModels.filter((e) => e.engine === InferenceEngine.nitro)
|
|
||||||
.length === 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
|
||||||
{messages.length === 0 ? (
|
|
||||||
<div className="mx-auto flex h-full w-3/4 flex-col items-center justify-center text-center">
|
|
||||||
<LogoMark
|
|
||||||
className="mx-auto mb-4 animate-wave"
|
|
||||||
width={56}
|
|
||||||
height={56}
|
|
||||||
/>
|
|
||||||
{showOnboardingStep ? (
|
|
||||||
<>
|
|
||||||
<p className="mt-1 text-base font-medium">
|
|
||||||
{`You don't have a local model yet.`}
|
|
||||||
</p>
|
|
||||||
<div className="w-auto px-4 py-2">
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
className="bg-blue-100 font-bold text-blue-600 hover:bg-blue-100 hover:text-blue-600"
|
|
||||||
onClick={() => setMainViewState(MainViewState.Hub)}
|
|
||||||
>
|
|
||||||
Explore The Hub
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className="mt-1 text-base font-medium">How can I help you?</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ScrollToBottom className="flex h-full w-full flex-col">
|
<ScrollToBottom className="flex h-full w-full flex-col">
|
||||||
{messages.map((message, index) => (
|
{messages.map((message, index) => (
|
||||||
<div key={message.id}>
|
<div key={message.id}>
|
||||||
@ -85,14 +31,10 @@ const ChatBody: React.FC = () => {
|
|||||||
|
|
||||||
{(message.status === MessageStatus.Error ||
|
{(message.status === MessageStatus.Error ||
|
||||||
message.status === MessageStatus.Stopped) &&
|
message.status === MessageStatus.Stopped) &&
|
||||||
index === messages.length - 1 && (
|
index === messages.length - 1 && <ErrorMessage message={message} />}
|
||||||
<ErrorMessage message={message} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ScrollToBottom>
|
</ScrollToBottom>
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,53 @@
|
|||||||
import SettingComponentBuilder from '../../Chat/ModelSetting/SettingComponent'
|
import { useCallback } from 'react'
|
||||||
import { SettingComponentData } from '../ModelSetting/SettingComponent'
|
|
||||||
|
import { SettingComponentProps } from '@janhq/core/.'
|
||||||
|
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||||
|
|
||||||
|
import SettingComponentBuilder from '../../Chat/ModelSetting/SettingComponent'
|
||||||
|
|
||||||
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
import {
|
||||||
|
activeThreadAtom,
|
||||||
|
engineParamsUpdateAtom,
|
||||||
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
componentData: SettingComponentProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const EngineSetting: React.FC<Props> = ({ componentData }) => {
|
||||||
|
const isLocalServerRunning = useAtomValue(serverEnabledAtom)
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
|
||||||
|
const { stopModel } = useActiveModel()
|
||||||
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
|
|
||||||
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
|
||||||
|
const onValueChanged = useCallback(
|
||||||
|
(key: string, value: string | number | boolean) => {
|
||||||
|
if (!activeThread) return
|
||||||
|
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
|
||||||
|
updateModelParameter(activeThread, {
|
||||||
|
params: { [key]: value },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[activeThread, setEngineParamsUpdate, stopModel, updateModelParameter]
|
||||||
|
)
|
||||||
|
|
||||||
const EngineSetting = ({
|
|
||||||
componentData,
|
|
||||||
enabled = true,
|
|
||||||
}: {
|
|
||||||
componentData: SettingComponentData[]
|
|
||||||
enabled?: boolean
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{componentData.filter((e) => e.name !== 'prompt_template').length && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<SettingComponentBuilder
|
<SettingComponentBuilder
|
||||||
componentData={componentData}
|
componentProps={componentData}
|
||||||
enabled={enabled}
|
enabled={!isLocalServerRunning}
|
||||||
selector={(e) => e.name !== 'prompt_template'}
|
onValueUpdated={onValueChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,6 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
import { getErrorTitle } from '@/utils/errorMessage'
|
|
||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
|
|
||||||
@ -31,10 +29,27 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
resendChatMessage(message)
|
resendChatMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorTitle = getErrorTitle(
|
const getErrorTitle = () => {
|
||||||
message.error_code ?? ErrorCode.Unknown,
|
switch (message.error_code) {
|
||||||
message.content[0]?.text?.value
|
case ErrorCode.Unknown:
|
||||||
|
return 'Apologies, something’s amiss!'
|
||||||
|
case ErrorCode.InvalidApiKey:
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Invalid API key. Please check your API key from{' '}
|
||||||
|
<button
|
||||||
|
className="font-medium text-primary dark:text-blue-400"
|
||||||
|
onClick={() => setMainState(MainViewState.Settings)}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</button>{' '}
|
||||||
|
and try again.
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
|
default:
|
||||||
|
return message.content[0]?.text?.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
@ -84,7 +99,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
Model is currently unavailable. Please switch to a different
|
Model is currently unavailable. Please switch to a different
|
||||||
model or install the{' '}
|
model or install the{' '}
|
||||||
<button
|
<button
|
||||||
className="font-medium text-blue-500"
|
className="font-medium text-primary dark:text-blue-400"
|
||||||
onClick={() => setMainState(MainViewState.Settings)}
|
onClick={() => setMainState(MainViewState.Settings)}
|
||||||
>
|
>
|
||||||
{loadModelError.split('::')[1] ?? ''}
|
{loadModelError.split('::')[1] ?? ''}
|
||||||
@ -97,7 +112,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
key={message.id}
|
key={message.id}
|
||||||
className="flex flex-col items-center text-center text-sm font-medium text-gray-500"
|
className="flex flex-col items-center text-center text-sm font-medium text-gray-500"
|
||||||
>
|
>
|
||||||
<p>{errorTitle}</p>
|
{getErrorTitle()}
|
||||||
<p>
|
<p>
|
||||||
Jan’s in beta. Access
|
Jan’s in beta. Access
|
||||||
<span
|
<span
|
||||||
|
|||||||
@ -1,149 +1,78 @@
|
|||||||
/* eslint-disable no-case-declarations */
|
import {
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
SettingComponentProps,
|
||||||
|
InputComponentProps,
|
||||||
|
CheckboxComponentProps,
|
||||||
|
SliderComponentProps,
|
||||||
|
} from '@janhq/core'
|
||||||
|
|
||||||
import Checkbox from '@/containers/Checkbox'
|
import Checkbox from '@/containers/Checkbox'
|
||||||
import ModelConfigInput from '@/containers/ModelConfigInput'
|
import ModelConfigInput from '@/containers/ModelConfigInput'
|
||||||
import SliderRightPanel from '@/containers/SliderRightPanel'
|
import SliderRightPanel from '@/containers/SliderRightPanel'
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
type Props = {
|
||||||
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
componentProps: SettingComponentProps[]
|
||||||
|
|
||||||
import { getConfigurationsData } from '@/utils/componentSettings'
|
|
||||||
import { toSettingParams } from '@/utils/modelParam'
|
|
||||||
|
|
||||||
import {
|
|
||||||
engineParamsUpdateAtom,
|
|
||||||
getActiveThreadIdAtom,
|
|
||||||
getActiveThreadModelParamsAtom,
|
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
|
||||||
|
|
||||||
export type ControllerType = 'slider' | 'checkbox' | 'input'
|
|
||||||
|
|
||||||
export type SettingComponentData = {
|
|
||||||
name: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
controllerType: ControllerType
|
|
||||||
controllerData: SliderData | CheckboxData | InputData
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InputData = {
|
|
||||||
placeholder: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SliderData = {
|
|
||||||
min: number
|
|
||||||
max: number
|
|
||||||
|
|
||||||
step: number
|
|
||||||
value: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckboxData = {
|
|
||||||
checked: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingComponent = ({
|
|
||||||
componentData,
|
|
||||||
enabled = true,
|
|
||||||
selector,
|
|
||||||
updater,
|
|
||||||
}: {
|
|
||||||
componentData: SettingComponentData[]
|
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
selector?: (e: SettingComponentData) => boolean
|
onValueUpdated: (key: string, value: string | number | boolean) => void
|
||||||
updater?: (
|
}
|
||||||
threadId: string,
|
|
||||||
name: string,
|
const SettingComponent: React.FC<Props> = ({
|
||||||
value: string | number | boolean | string[]
|
componentProps,
|
||||||
) => void
|
enabled = true,
|
||||||
|
onValueUpdated,
|
||||||
}) => {
|
}) => {
|
||||||
const { updateModelParameter } = useUpdateModelParameters()
|
const components = componentProps.map((data) => {
|
||||||
|
|
||||||
const threadId = useAtomValue(getActiveThreadIdAtom)
|
|
||||||
|
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
|
||||||
|
|
||||||
const modelSettingParams = toSettingParams(activeModelParams)
|
|
||||||
|
|
||||||
const engineParams = getConfigurationsData(modelSettingParams)
|
|
||||||
|
|
||||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
|
||||||
|
|
||||||
const { stopModel } = useActiveModel()
|
|
||||||
|
|
||||||
const onValueChanged = (
|
|
||||||
name: string,
|
|
||||||
value: string | number | boolean | string[]
|
|
||||||
) => {
|
|
||||||
if (!threadId) return
|
|
||||||
if (engineParams.some((x) => x.name.includes(name))) {
|
|
||||||
setEngineParamsUpdate(true)
|
|
||||||
stopModel()
|
|
||||||
} else {
|
|
||||||
setEngineParamsUpdate(false)
|
|
||||||
}
|
|
||||||
if (updater) updater(threadId, name, value)
|
|
||||||
else {
|
|
||||||
// Convert stop string to array
|
|
||||||
if (name === 'stop' && typeof value === 'string') {
|
|
||||||
value = [value]
|
|
||||||
}
|
|
||||||
updateModelParameter(threadId, {
|
|
||||||
params: { [name]: value },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const components = componentData
|
|
||||||
.filter((x) => (selector ? selector(x) : true))
|
|
||||||
.map((data) => {
|
|
||||||
switch (data.controllerType) {
|
switch (data.controllerType) {
|
||||||
case 'slider':
|
case 'slider': {
|
||||||
const { min, max, step, value } = data.controllerData as SliderData
|
const { min, max, step, value } =
|
||||||
|
data.controllerProps as SliderComponentProps
|
||||||
return (
|
return (
|
||||||
<SliderRightPanel
|
<SliderRightPanel
|
||||||
key={data.name}
|
key={data.key}
|
||||||
title={data.title}
|
title={data.title}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
step={step}
|
step={step}
|
||||||
value={value}
|
value={value}
|
||||||
name={data.name}
|
name={data.key}
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
onValueChanged={(value) => onValueChanged(data.name, value)}
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'input':
|
}
|
||||||
|
|
||||||
|
case 'input': {
|
||||||
const { placeholder, value: textValue } =
|
const { placeholder, value: textValue } =
|
||||||
data.controllerData as InputData
|
data.controllerProps as InputComponentProps
|
||||||
return (
|
return (
|
||||||
<ModelConfigInput
|
<ModelConfigInput
|
||||||
title={data.title}
|
title={data.title}
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
key={data.name}
|
key={data.key}
|
||||||
name={data.name}
|
name={data.key}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={textValue}
|
value={textValue}
|
||||||
onValueChanged={(value) => onValueChanged(data.name, value)}
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'checkbox':
|
}
|
||||||
const { checked } = data.controllerData as CheckboxData
|
|
||||||
|
case 'checkbox': {
|
||||||
|
const { value } = data.controllerProps as CheckboxComponentProps
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={data.name}
|
key={data.key}
|
||||||
enabled={enabled}
|
enabled={enabled}
|
||||||
name={data.name}
|
name={data.key}
|
||||||
description={data.description}
|
description={data.description}
|
||||||
title={data.title}
|
title={data.title}
|
||||||
checked={checked}
|
checked={value}
|
||||||
onValueChanged={(value) => onValueChanged(data.name, value)}
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,48 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
import React, { useCallback } from 'react'
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import SettingComponentBuilder, {
|
import { SettingComponentProps } from '@janhq/core/.'
|
||||||
SettingComponentData,
|
|
||||||
} from './SettingComponent'
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||||
|
|
||||||
|
import SettingComponentBuilder from './SettingComponent'
|
||||||
|
|
||||||
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
componentProps: SettingComponentProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelSetting: React.FC<Props> = ({ componentProps }) => {
|
||||||
|
const isLocalServerRunning = useAtomValue(serverEnabledAtom)
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
|
|
||||||
|
const onValueChanged = useCallback(
|
||||||
|
(key: string, value: string | number | boolean) => {
|
||||||
|
if (!activeThread) return
|
||||||
|
|
||||||
|
if (key === 'stop' && typeof value === 'string') {
|
||||||
|
updateModelParameter(activeThread, {
|
||||||
|
params: { [key]: [value] },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
updateModelParameter(activeThread, {
|
||||||
|
params: { [key]: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeThread, updateModelParameter]
|
||||||
|
)
|
||||||
|
|
||||||
const ModelSetting = ({
|
|
||||||
componentData,
|
|
||||||
}: {
|
|
||||||
componentData: SettingComponentData[]
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{componentData.filter((e) => e.name !== 'prompt_template').length && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<SettingComponentBuilder
|
<SettingComponentBuilder
|
||||||
componentData={componentData}
|
enabled={!isLocalServerRunning}
|
||||||
selector={(e) => e.name !== 'prompt_template'}
|
componentProps={componentProps}
|
||||||
|
onValueUpdated={onValueChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,192 +1,224 @@
|
|||||||
import { SettingComponentData } from './SettingComponent'
|
import { SettingComponentProps } from '@janhq/core/.'
|
||||||
|
|
||||||
export const presetConfiguration: Record<string, SettingComponentData> = {
|
export const presetConfiguration: Record<string, SettingComponentProps> = {
|
||||||
prompt_template: {
|
prompt_template: {
|
||||||
name: 'prompt_template',
|
key: 'prompt_template',
|
||||||
title: 'Prompt template',
|
title: 'Prompt template',
|
||||||
description: 'The prompt to use for internal configuration.',
|
description: 'The prompt to use for internal configuration.',
|
||||||
controllerType: 'input',
|
controllerType: 'input',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
placeholder: 'Prompt template',
|
placeholder: 'Prompt template',
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
stop: {
|
stop: {
|
||||||
name: 'stop',
|
key: 'stop',
|
||||||
title: 'Stop',
|
title: 'Stop',
|
||||||
description:
|
description:
|
||||||
'Defines specific tokens or phrases at which the model will stop generating further output. ',
|
'Defines specific tokens or phrases at which the model will stop generating further output. ',
|
||||||
controllerType: 'input',
|
controllerType: 'input',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
placeholder: 'Stop',
|
placeholder: 'Stop',
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
ctx_len: {
|
ctx_len: {
|
||||||
name: 'ctx_len',
|
key: 'ctx_len',
|
||||||
title: 'Context Length',
|
title: 'Context Length',
|
||||||
description:
|
description:
|
||||||
'The context length for model operations varies; the maximum depends on the specific model used.',
|
'The context length for model operations varies; the maximum depends on the specific model used.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 4096,
|
max: 4096,
|
||||||
step: 128,
|
step: 128,
|
||||||
value: 4096,
|
value: 4096,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
max_tokens: {
|
max_tokens: {
|
||||||
name: 'max_tokens',
|
key: 'max_tokens',
|
||||||
title: 'Max Tokens',
|
title: 'Max Tokens',
|
||||||
description:
|
description:
|
||||||
'The maximum number of tokens the model will generate in a single response.',
|
'The maximum number of tokens the model will generate in a single response.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 100,
|
min: 100,
|
||||||
max: 4096,
|
max: 4096,
|
||||||
step: 10,
|
step: 10,
|
||||||
value: 4096,
|
value: 4096,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
ngl: {
|
ngl: {
|
||||||
name: 'ngl',
|
key: 'ngl',
|
||||||
title: 'Number of GPU layers (ngl)',
|
title: 'Number of GPU layers (ngl)',
|
||||||
description: 'The number of layers to load onto the GPU for acceleration.',
|
description: 'The number of layers to load onto the GPU for acceleration.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 100,
|
max: 100,
|
||||||
step: 1,
|
step: 1,
|
||||||
value: 100,
|
value: 100,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
embedding: {
|
embedding: {
|
||||||
name: 'embedding',
|
key: 'embedding',
|
||||||
title: 'Embedding',
|
title: 'Embedding',
|
||||||
description: 'Whether to enable embedding.',
|
description: 'Whether to enable embedding.',
|
||||||
controllerType: 'checkbox',
|
controllerType: 'checkbox',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
checked: true,
|
value: true,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
stream: {
|
stream: {
|
||||||
name: 'stream',
|
key: 'stream',
|
||||||
title: 'Stream',
|
title: 'Stream',
|
||||||
description: 'Enable real-time data processing for faster predictions.',
|
description: 'Enable real-time data processing for faster predictions.',
|
||||||
controllerType: 'checkbox',
|
controllerType: 'checkbox',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
checked: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
name: 'temperature',
|
key: 'temperature',
|
||||||
title: 'Temperature',
|
title: 'Temperature',
|
||||||
description: 'Controls the randomness of the model’s output.',
|
description: 'Controls the randomness of the model’s output.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 2,
|
max: 2,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
value: 0.7,
|
value: 0.7,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
frequency_penalty: {
|
frequency_penalty: {
|
||||||
name: 'frequency_penalty',
|
key: 'frequency_penalty',
|
||||||
title: 'Frequency Penalty',
|
title: 'Frequency Penalty',
|
||||||
description:
|
description:
|
||||||
'Adjusts the likelihood of the model repeating words or phrases in its output. ',
|
'Adjusts the likelihood of the model repeating words or phrases in its output. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
value: 0.7,
|
value: 0.7,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
presence_penalty: {
|
presence_penalty: {
|
||||||
name: 'presence_penalty',
|
key: 'presence_penalty',
|
||||||
title: 'Presence Penalty',
|
title: 'Presence Penalty',
|
||||||
description:
|
description:
|
||||||
'Influences the generation of new and varied concepts in the model’s output. ',
|
'Influences the generation of new and varied concepts in the model’s output. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
value: 0.7,
|
value: 0.7,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
top_p: {
|
top_p: {
|
||||||
name: 'top_p',
|
key: 'top_p',
|
||||||
title: 'Top P',
|
title: 'Top P',
|
||||||
description: 'Set probability threshold for more relevant outputs.',
|
description: 'Set probability threshold for more relevant outputs.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
value: 0.95,
|
value: 0.95,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
n_parallel: {
|
n_parallel: {
|
||||||
name: 'n_parallel',
|
key: 'n_parallel',
|
||||||
title: 'N Parallel',
|
title: 'N Parallel',
|
||||||
description:
|
description:
|
||||||
'The number of parallel operations. Only set when enable continuous batching. ',
|
'The number of parallel operations. Only set when enable continuous batching. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 4,
|
max: 4,
|
||||||
step: 1,
|
step: 1,
|
||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
// assistant
|
// assistant
|
||||||
chunk_size: {
|
chunk_size: {
|
||||||
name: 'chunk_size',
|
key: 'chunk_size',
|
||||||
title: 'Chunk Size',
|
title: 'Chunk Size',
|
||||||
description: 'Maximum number of tokens in a chunk',
|
description: 'Maximum number of tokens in a chunk',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 128,
|
min: 128,
|
||||||
max: 2048,
|
max: 2048,
|
||||||
step: 128,
|
step: 128,
|
||||||
value: 1024,
|
value: 1024,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
chunk_overlap: {
|
chunk_overlap: {
|
||||||
name: 'chunk_overlap',
|
key: 'chunk_overlap',
|
||||||
title: 'Chunk Overlap',
|
title: 'Chunk Overlap',
|
||||||
description: 'Number of tokens overlapping between two adjacent chunks',
|
description: 'Number of tokens overlapping between two adjacent chunks',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 32,
|
min: 32,
|
||||||
max: 512,
|
max: 512,
|
||||||
step: 32,
|
step: 32,
|
||||||
value: 64,
|
value: 64,
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
top_k: {
|
top_k: {
|
||||||
name: 'top_k',
|
key: 'top_k',
|
||||||
title: 'Top K',
|
title: 'Top K',
|
||||||
description: 'Number of top-ranked documents to retrieve',
|
description: 'Number of top-ranked documents to retrieve',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 5,
|
max: 5,
|
||||||
step: 1,
|
step: 1,
|
||||||
value: 2,
|
value: 2,
|
||||||
},
|
},
|
||||||
|
requireModelReload: false,
|
||||||
|
configType: 'runtime',
|
||||||
},
|
},
|
||||||
retrieval_template: {
|
retrieval_template: {
|
||||||
name: 'retrieval_template',
|
key: 'retrieval_template',
|
||||||
title: 'Retrieval Template',
|
title: 'Retrieval Template',
|
||||||
description:
|
description:
|
||||||
'The template to use for retrieval. The following variables are available: {CONTEXT}, {QUESTION}',
|
'The template to use for retrieval. The following variables are available: {CONTEXT}, {QUESTION}',
|
||||||
controllerType: 'input',
|
controllerType: 'input',
|
||||||
controllerData: {
|
controllerProps: {
|
||||||
placeholder: 'Retrieval Template',
|
placeholder: 'Retrieval Template',
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
|
requireModelReload: true,
|
||||||
|
configType: 'setting',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
196
web/screens/Chat/Sidebar/AssistantTool/index.tsx
Normal file
196
web/screens/Chat/Sidebar/AssistantTool/index.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { Fragment, useCallback } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipTrigger,
|
||||||
|
TooltipPortal,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipArrow,
|
||||||
|
Switch,
|
||||||
|
Input,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import { InfoIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import CardSidebar from '@/containers/CardSidebar'
|
||||||
|
|
||||||
|
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||||
|
|
||||||
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
|
|
||||||
|
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||||
|
|
||||||
|
import AssistantSetting from '../../AssistantSetting'
|
||||||
|
|
||||||
|
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||||
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
|
const AssistantTool: React.FC = () => {
|
||||||
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
|
|
||||||
|
const componentDataAssistantSetting = getConfigurationsData(
|
||||||
|
(activeThread?.assistants[0]?.tools &&
|
||||||
|
activeThread?.assistants[0]?.tools[0]?.settings) ??
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const onRetrievalSwitchUpdate = useCallback(
|
||||||
|
(enabled: boolean) => {
|
||||||
|
if (!activeThread) return
|
||||||
|
updateThreadMetadata({
|
||||||
|
...activeThread,
|
||||||
|
assistants: [
|
||||||
|
{
|
||||||
|
...activeThread.assistants[0],
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
type: 'retrieval',
|
||||||
|
enabled: enabled,
|
||||||
|
settings:
|
||||||
|
(activeThread.assistants[0].tools &&
|
||||||
|
activeThread.assistants[0].tools[0]?.settings) ??
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[activeThread, updateThreadMetadata]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!experimentalFeature) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{activeThread?.assistants[0]?.tools &&
|
||||||
|
componentDataAssistantSetting.length > 0 && (
|
||||||
|
<CardSidebar title="Tools" isShow={true}>
|
||||||
|
<div className="px-2 pt-4">
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label
|
||||||
|
id="retrieval"
|
||||||
|
className="inline-flex items-center font-bold text-zinc-500 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Retrieval
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="ml-2 flex-shrink-0 text-black dark:text-gray-500"
|
||||||
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
<span>
|
||||||
|
Retrieval helps the assistant use information from
|
||||||
|
files you send to it. Once you share a file, the
|
||||||
|
assistant automatically fetches the relevant content
|
||||||
|
based on your request.
|
||||||
|
</span>
|
||||||
|
<TooltipArrow />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Switch
|
||||||
|
name="retrieval"
|
||||||
|
className="mr-2"
|
||||||
|
checked={activeThread?.assistants[0].tools[0].enabled}
|
||||||
|
onCheckedChange={onRetrievalSwitchUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{activeThread?.assistants[0]?.tools[0].enabled && (
|
||||||
|
<div className="pb-4 pt-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="item-center mb-2 flex">
|
||||||
|
<label
|
||||||
|
id="embedding-model"
|
||||||
|
className="inline-flex font-bold text-zinc-500 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Embedding Model
|
||||||
|
</label>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
||||||
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
<span>
|
||||||
|
Embedding model is crucial for understanding and
|
||||||
|
processing the input text effectively by
|
||||||
|
converting text to numerical representations.
|
||||||
|
Align the model choice with your task, evaluate
|
||||||
|
its performance, and consider factors like
|
||||||
|
resource availability. Experiment to find the best
|
||||||
|
fit for your specific use case.
|
||||||
|
</span>
|
||||||
|
<TooltipArrow />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Input value={selectedModel?.name} disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="mb-2 flex items-center">
|
||||||
|
<label
|
||||||
|
id="vector-database"
|
||||||
|
className="inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Vector Database
|
||||||
|
</label>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<InfoIcon
|
||||||
|
size={16}
|
||||||
|
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
||||||
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent side="top" className="max-w-[240px]">
|
||||||
|
<span>
|
||||||
|
Vector Database is crucial for efficient storage
|
||||||
|
and retrieval of embeddings. Consider your
|
||||||
|
specific task, available resources, and language
|
||||||
|
requirements. Experiment to find the best fit for
|
||||||
|
your specific use case.
|
||||||
|
</span>
|
||||||
|
<TooltipArrow />
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Input value="HNSWLib" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AssistantSetting
|
||||||
|
componentData={componentDataAssistantSetting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardSidebar>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssistantTool
|
||||||
50
web/screens/Chat/Sidebar/PromptTemplateSetting/index.tsx
Normal file
50
web/screens/Chat/Sidebar/PromptTemplateSetting/index.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { SettingComponentProps } from '@janhq/core'
|
||||||
|
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||||
|
|
||||||
|
import SettingComponent from '../../ModelSetting/SettingComponent'
|
||||||
|
|
||||||
|
import {
|
||||||
|
activeThreadAtom,
|
||||||
|
engineParamsUpdateAtom,
|
||||||
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
componentData: SettingComponentProps[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const PromptTemplateSetting: React.FC<Props> = ({ componentData }) => {
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
|
||||||
|
const { stopModel } = useActiveModel()
|
||||||
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
|
|
||||||
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
const onValueChanged = useCallback(
|
||||||
|
(key: string, value: string | number | boolean) => {
|
||||||
|
if (!activeThread) return
|
||||||
|
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
|
||||||
|
updateModelParameter(activeThread, {
|
||||||
|
params: { [key]: value },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[activeThread, setEngineParamsUpdate, stopModel, updateModelParameter]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingComponent
|
||||||
|
componentProps={componentData}
|
||||||
|
onValueUpdated={onValueChanged}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PromptTemplateSetting
|
||||||
@ -1,19 +1,9 @@
|
|||||||
import React from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
import {
|
import { Input, Textarea } from '@janhq/uikit'
|
||||||
Input,
|
|
||||||
Textarea,
|
|
||||||
Switch,
|
|
||||||
Tooltip,
|
|
||||||
TooltipArrow,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipPortal,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@janhq/uikit'
|
|
||||||
|
|
||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { InfoIcon } from 'lucide-react'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
@ -28,13 +18,13 @@ import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
|||||||
import { getConfigurationsData } from '@/utils/componentSettings'
|
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||||
import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
||||||
|
|
||||||
import AssistantSetting from '../AssistantSetting'
|
|
||||||
import EngineSetting from '../EngineSetting'
|
import EngineSetting from '../EngineSetting'
|
||||||
import ModelSetting from '../ModelSetting'
|
import ModelSetting from '../ModelSetting'
|
||||||
|
|
||||||
import SettingComponentBuilder from '../ModelSetting/SettingComponent'
|
import AssistantTool from './AssistantTool'
|
||||||
|
|
||||||
|
import PromptTemplateSetting from './PromptTemplateSetting'
|
||||||
|
|
||||||
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
getActiveThreadModelParamsAtom,
|
getActiveThreadModelParamsAtom,
|
||||||
@ -48,23 +38,63 @@ const Sidebar: React.FC = () => {
|
|||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
|
||||||
|
|
||||||
const modelEngineParams = toSettingParams(activeModelParams)
|
const modelSettings = useMemo(() => {
|
||||||
const modelRuntimeParams = toRuntimeParams(activeModelParams)
|
const modelRuntimeParams = toRuntimeParams(activeModelParams)
|
||||||
const componentDataAssistantSetting = getConfigurationsData(
|
|
||||||
(activeThread?.assistants[0]?.tools &&
|
const componentDataRuntimeSetting = getConfigurationsData(
|
||||||
activeThread?.assistants[0]?.tools[0]?.settings) ??
|
modelRuntimeParams,
|
||||||
{}
|
selectedModel
|
||||||
)
|
)
|
||||||
|
return componentDataRuntimeSetting.filter(
|
||||||
|
(x) => x.key !== 'prompt_template'
|
||||||
|
)
|
||||||
|
}, [activeModelParams, selectedModel])
|
||||||
|
|
||||||
|
const engineSettings = useMemo(() => {
|
||||||
|
const modelEngineParams = toSettingParams(activeModelParams)
|
||||||
const componentDataEngineSetting = getConfigurationsData(
|
const componentDataEngineSetting = getConfigurationsData(
|
||||||
modelEngineParams,
|
modelEngineParams,
|
||||||
selectedModel
|
selectedModel
|
||||||
)
|
)
|
||||||
const componentDataRuntimeSetting = getConfigurationsData(
|
return componentDataEngineSetting.filter((x) => x.key !== 'prompt_template')
|
||||||
modelRuntimeParams,
|
}, [activeModelParams, selectedModel])
|
||||||
|
|
||||||
|
const promptTemplateSettings = useMemo(() => {
|
||||||
|
const modelEngineParams = toSettingParams(activeModelParams)
|
||||||
|
const componentDataEngineSetting = getConfigurationsData(
|
||||||
|
modelEngineParams,
|
||||||
selectedModel
|
selectedModel
|
||||||
)
|
)
|
||||||
|
return componentDataEngineSetting.filter((x) => x.key === 'prompt_template')
|
||||||
|
}, [activeModelParams, selectedModel])
|
||||||
|
|
||||||
|
const onThreadTitleChanged = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (activeThread)
|
||||||
|
updateThreadMetadata({
|
||||||
|
...activeThread,
|
||||||
|
title: e.target.value || '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[activeThread, updateThreadMetadata]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onAssistantInstructionChanged = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (activeThread)
|
||||||
|
updateThreadMetadata({
|
||||||
|
...activeThread,
|
||||||
|
assistants: [
|
||||||
|
{
|
||||||
|
...activeThread.assistants[0],
|
||||||
|
instructions: e.target.value || '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[activeThread, updateThreadMetadata]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -92,13 +122,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<Input
|
<Input
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
value={activeThread?.title}
|
value={activeThread?.title}
|
||||||
onChange={(e) => {
|
onChange={onThreadTitleChanged}
|
||||||
if (activeThread)
|
|
||||||
updateThreadMetadata({
|
|
||||||
...activeThread,
|
|
||||||
title: e.target.value || '',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@ -133,18 +157,7 @@ const Sidebar: React.FC = () => {
|
|||||||
id="assistant-instructions"
|
id="assistant-instructions"
|
||||||
placeholder="Eg. You are a helpful assistant."
|
placeholder="Eg. You are a helpful assistant."
|
||||||
value={activeThread?.assistants[0].instructions ?? ''}
|
value={activeThread?.assistants[0].instructions ?? ''}
|
||||||
onChange={(e) => {
|
onChange={onAssistantInstructionChanged}
|
||||||
if (activeThread)
|
|
||||||
updateThreadMetadata({
|
|
||||||
...activeThread,
|
|
||||||
assistants: [
|
|
||||||
{
|
|
||||||
...activeThread.assistants[0],
|
|
||||||
instructions: e.target.value || '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -154,36 +167,33 @@ const Sidebar: React.FC = () => {
|
|||||||
<div className="px-2 pt-4">
|
<div className="px-2 pt-4">
|
||||||
<DropdownListSidebar />
|
<DropdownListSidebar />
|
||||||
|
|
||||||
{componentDataRuntimeSetting.length > 0 && (
|
{modelSettings.length > 0 && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<CardSidebar title="Inference Parameters" asChild>
|
<CardSidebar title="Inference Parameters" asChild>
|
||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<ModelSetting componentData={componentDataRuntimeSetting} />
|
<ModelSetting componentProps={modelSettings} />
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{componentDataEngineSetting.filter(
|
{promptTemplateSettings.length > 0 && (
|
||||||
(x) => x.name === 'prompt_template'
|
|
||||||
).length !== 0 && (
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<CardSidebar title="Model Parameters" asChild>
|
<CardSidebar title="Model Parameters" asChild>
|
||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<SettingComponentBuilder
|
<PromptTemplateSetting
|
||||||
componentData={componentDataEngineSetting}
|
componentData={promptTemplateSettings}
|
||||||
selector={(x) => x.name === 'prompt_template'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{componentDataEngineSetting.length > 0 && (
|
{engineSettings.length > 0 && (
|
||||||
<div className="my-4">
|
<div className="my-4">
|
||||||
<CardSidebar title="Engine Parameters" asChild>
|
<CardSidebar title="Engine Parameters" asChild>
|
||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<EngineSetting componentData={componentDataEngineSetting} />
|
<EngineSetting componentData={engineSettings} />
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
</div>
|
</div>
|
||||||
@ -191,166 +201,7 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
|
|
||||||
{experimentalFeature && (
|
<AssistantTool />
|
||||||
<div>
|
|
||||||
{activeThread?.assistants[0]?.tools &&
|
|
||||||
componentDataAssistantSetting.length > 0 && (
|
|
||||||
<CardSidebar title="Tools" isShow={true}>
|
|
||||||
<div className="px-2 pt-4">
|
|
||||||
<div className="mb-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label
|
|
||||||
id="retrieval"
|
|
||||||
className="inline-flex items-center font-bold text-zinc-500 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Retrieval
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<InfoIcon
|
|
||||||
size={16}
|
|
||||||
className="ml-2 flex-shrink-0 text-black dark:text-gray-500"
|
|
||||||
/>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent
|
|
||||||
side="top"
|
|
||||||
className="max-w-[240px]"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Retrieval helps the assistant use information
|
|
||||||
from files you send to it. Once you share a
|
|
||||||
file, the assistant automatically fetches the
|
|
||||||
relevant content based on your request.
|
|
||||||
</span>
|
|
||||||
<TooltipArrow />
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Switch
|
|
||||||
name="retrieval"
|
|
||||||
className="mr-2"
|
|
||||||
checked={
|
|
||||||
activeThread?.assistants[0].tools[0].enabled
|
|
||||||
}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
if (activeThread)
|
|
||||||
updateThreadMetadata({
|
|
||||||
...activeThread,
|
|
||||||
assistants: [
|
|
||||||
{
|
|
||||||
...activeThread.assistants[0],
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
type: 'retrieval',
|
|
||||||
enabled: e,
|
|
||||||
settings:
|
|
||||||
(activeThread.assistants[0].tools &&
|
|
||||||
activeThread.assistants[0]
|
|
||||||
.tools[0]?.settings) ??
|
|
||||||
{},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{activeThread?.assistants[0]?.tools[0].enabled && (
|
|
||||||
<div className="pb-4 pt-2">
|
|
||||||
<div className="mb-4">
|
|
||||||
<div className="item-center mb-2 flex">
|
|
||||||
<label
|
|
||||||
id="embedding-model"
|
|
||||||
className="inline-flex font-bold text-zinc-500 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Embedding Model
|
|
||||||
</label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<InfoIcon
|
|
||||||
size={16}
|
|
||||||
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
|
||||||
/>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent
|
|
||||||
side="top"
|
|
||||||
className="max-w-[240px]"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Embedding model is crucial for understanding
|
|
||||||
and processing the input text effectively by
|
|
||||||
converting text to numerical
|
|
||||||
representations. Align the model choice with
|
|
||||||
your task, evaluate its performance, and
|
|
||||||
consider factors like resource availability.
|
|
||||||
Experiment to find the best fit for your
|
|
||||||
specific use case.
|
|
||||||
</span>
|
|
||||||
<TooltipArrow />
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Input value={selectedModel?.name} disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<div className="mb-2 flex items-center">
|
|
||||||
<label
|
|
||||||
id="vector-database"
|
|
||||||
className="inline-block font-bold text-zinc-500 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Vector Database
|
|
||||||
</label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<InfoIcon
|
|
||||||
size={16}
|
|
||||||
className="ml-2 flex-shrink-0 dark:text-gray-500"
|
|
||||||
/>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent
|
|
||||||
side="top"
|
|
||||||
className="max-w-[240px]"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Vector Database is crucial for efficient
|
|
||||||
storage and retrieval of embeddings.
|
|
||||||
Consider your specific task, available
|
|
||||||
resources, and language requirements.
|
|
||||||
Experiment to find the best fit for your
|
|
||||||
specific use case.
|
|
||||||
</span>
|
|
||||||
<TooltipArrow />
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Input value="HNSWLib" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<AssistantSetting
|
|
||||||
componentData={componentDataAssistantSetting}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -49,8 +49,7 @@ import { toSettingParams } from '@/utils/modelParam'
|
|||||||
|
|
||||||
import EngineSetting from '../Chat/EngineSetting'
|
import EngineSetting from '../Chat/EngineSetting'
|
||||||
|
|
||||||
import SettingComponentBuilder from '../Chat/ModelSetting/SettingComponent'
|
import ModelSetting from '../Chat/ModelSetting'
|
||||||
|
|
||||||
import { showRightSideBarAtom } from '../Chat/Sidebar'
|
import { showRightSideBarAtom } from '../Chat/Sidebar'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -429,17 +428,22 @@ const LocalServerScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{componentDataEngineSetting.filter(
|
{componentDataEngineSetting.filter((x) => x.key === 'prompt_template')
|
||||||
(x) => x.name === 'prompt_template'
|
.length !== 0 && (
|
||||||
).length !== 0 && (
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<CardSidebar title="Model Parameters" asChild>
|
<CardSidebar title="Model Parameters" asChild>
|
||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<SettingComponentBuilder
|
<ModelSetting componentProps={componentDataEngineSetting} />
|
||||||
|
{/* <SettingComponentBuilder
|
||||||
enabled={!serverEnabled}
|
enabled={!serverEnabled}
|
||||||
componentData={componentDataEngineSetting}
|
componentProps={componentDataEngineSetting}
|
||||||
selector={(x) => x.name === 'prompt_template'}
|
onValueUpdated={function (
|
||||||
/>
|
key: string,
|
||||||
|
value: string | number | boolean
|
||||||
|
): void {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
</div>
|
</div>
|
||||||
@ -449,10 +453,7 @@ const LocalServerScreen = () => {
|
|||||||
<div className="my-4">
|
<div className="my-4">
|
||||||
<CardSidebar title="Engine Parameters" asChild>
|
<CardSidebar title="Engine Parameters" asChild>
|
||||||
<div className="px-2 py-4">
|
<div className="px-2 py-4">
|
||||||
<EngineSetting
|
<EngineSetting componentData={componentDataEngineSetting} />
|
||||||
enabled={!serverEnabled}
|
|
||||||
componentData={componentDataEngineSetting}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
62
web/screens/Settings/ExtensionSetting/index.tsx
Normal file
62
web/screens/Settings/ExtensionSetting/index.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { SettingComponentProps } from '@janhq/core/.'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import SettingDetailItem from '../SettingDetail/SettingDetailItem'
|
||||||
|
|
||||||
|
import { extensionManager } from '@/extension'
|
||||||
|
import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
|
const ExtensionSetting: React.FC = () => {
|
||||||
|
const selectedExtensionName = useAtomValue(selectedSettingAtom)
|
||||||
|
const [settings, setSettings] = useState<SettingComponentProps[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getExtensionSettings = async () => {
|
||||||
|
if (!selectedExtensionName) return
|
||||||
|
const allSettings: SettingComponentProps[] = []
|
||||||
|
const baseExtension = extensionManager.get(selectedExtensionName)
|
||||||
|
if (!baseExtension) return
|
||||||
|
if (typeof baseExtension.getSettings === 'function') {
|
||||||
|
const setting = await baseExtension.getSettings()
|
||||||
|
if (setting) allSettings.push(...setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSettings(allSettings)
|
||||||
|
}
|
||||||
|
getExtensionSettings()
|
||||||
|
}, [selectedExtensionName])
|
||||||
|
|
||||||
|
const onValueChanged = async (
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean
|
||||||
|
) => {
|
||||||
|
// find the key in settings state, update it and set the state back
|
||||||
|
const newSettings = settings.map((setting) => {
|
||||||
|
if (setting.key !== key) return setting
|
||||||
|
setting.controllerProps.value = value
|
||||||
|
|
||||||
|
const extensionName = setting.extensionName
|
||||||
|
if (extensionName) {
|
||||||
|
extensionManager.get(extensionName)?.updateSettings([setting])
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting
|
||||||
|
})
|
||||||
|
|
||||||
|
setSettings(newSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingDetailItem
|
||||||
|
componentProps={settings}
|
||||||
|
onValueUpdated={onValueChanged}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtensionSetting
|
||||||
@ -66,7 +66,7 @@ const SelectingModelModal: React.FC = () => {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`flex h-[172px] w-full items-center justify-center rounded-md border ${borderColor} ${dragAndDropBgColor}`}
|
className={`flex h-[172px] w-full cursor-pointer items-center justify-center rounded-md border ${borderColor} ${dragAndDropBgColor}`}
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
onClick={onSelectFileClick}
|
onClick={onSelectFileClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { InputComponentProps, SettingComponentProps } from '@janhq/core'
|
||||||
|
import { Input } from '@janhq/uikit'
|
||||||
|
import { Marked, Renderer } from 'marked'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
settingProps: SettingComponentProps
|
||||||
|
onValueChanged?: (e: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const marked: Marked = new Marked({
|
||||||
|
renderer: {
|
||||||
|
link: (href, title, text) => {
|
||||||
|
return Renderer.prototype.link
|
||||||
|
?.apply(this, [href, title, text])
|
||||||
|
.replace('<a', "<a class='text-blue-500' target='_blank'")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const SettingDetailTextInputItem: React.FC<Props> = ({
|
||||||
|
settingProps,
|
||||||
|
onValueChanged,
|
||||||
|
}) => {
|
||||||
|
const { value, type, placeholder } =
|
||||||
|
settingProps.controllerProps as InputComponentProps
|
||||||
|
|
||||||
|
const description = marked.parse(settingProps.description ?? '', {
|
||||||
|
async: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full justify-between py-6">
|
||||||
|
<div className="flex flex-1 flex-col space-y-1">
|
||||||
|
<h1 className="text-base font-bold">{settingProps.title}</h1>
|
||||||
|
{
|
||||||
|
<div
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
dangerouslySetInnerHTML={{ __html: description }}
|
||||||
|
className="text-sm font-normal text-muted-foreground"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder}
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
className="ml-4 w-[360px]"
|
||||||
|
onChange={(e) => onValueChanged?.(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingDetailTextInputItem
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { SettingComponentProps } from '@janhq/core'
|
||||||
|
|
||||||
|
import SettingDetailTextInputItem from './SettingDetailTextInputItem'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
componentProps: SettingComponentProps[]
|
||||||
|
onValueUpdated: (key: string, value: string | number | boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingDetailItem: React.FC<Props> = ({
|
||||||
|
componentProps,
|
||||||
|
onValueUpdated,
|
||||||
|
}) => {
|
||||||
|
const components = componentProps.map((data) => {
|
||||||
|
switch (data.controllerType) {
|
||||||
|
case 'input': {
|
||||||
|
return (
|
||||||
|
<SettingDetailTextInputItem
|
||||||
|
key={data.key}
|
||||||
|
settingProps={data}
|
||||||
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
{components.map((component, index) => (
|
||||||
|
<div
|
||||||
|
className={`mx-6 ${index === components.length - 1 ? '' : 'border-b border-border'}`}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{component}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingDetailItem
|
||||||
32
web/screens/Settings/SettingDetail/index.tsx
Normal file
32
web/screens/Settings/SettingDetail/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import Advanced from '../Advanced'
|
||||||
|
import AppearanceOptions from '../Appearance'
|
||||||
|
import ExtensionCatalog from '../CoreExtensions'
|
||||||
|
import ExtensionSetting from '../ExtensionSetting'
|
||||||
|
import Models from '../Models'
|
||||||
|
|
||||||
|
import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
|
const SettingDetail: React.FC = () => {
|
||||||
|
const selectedSetting = useAtomValue(selectedSettingAtom)
|
||||||
|
|
||||||
|
switch (selectedSetting) {
|
||||||
|
case 'Extensions':
|
||||||
|
return <ExtensionCatalog />
|
||||||
|
|
||||||
|
case 'My Settings':
|
||||||
|
return <AppearanceOptions />
|
||||||
|
|
||||||
|
case 'Advanced Settings':
|
||||||
|
return <Advanced />
|
||||||
|
|
||||||
|
case 'My Models':
|
||||||
|
return <Models />
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <ExtensionSetting />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingDetail
|
||||||
44
web/screens/Settings/SettingMenu/SettingItem/index.tsx
Normal file
44
web/screens/Settings/SettingMenu/SettingItem/index.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import { motion as m } from 'framer-motion'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import { formatExtensionsName } from '@/utils/converter'
|
||||||
|
|
||||||
|
import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setting: string
|
||||||
|
extension?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingItem: React.FC<Props> = ({ setting, extension = false }) => {
|
||||||
|
const [selectedSetting, setSelectedSetting] = useAtom(selectedSettingAtom)
|
||||||
|
const isActive = selectedSetting === setting
|
||||||
|
|
||||||
|
const onSettingItemClick = useCallback(() => {
|
||||||
|
setSelectedSetting(setting)
|
||||||
|
}, [setting, setSelectedSetting])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative block cursor-pointer py-1.5"
|
||||||
|
onClick={onSettingItemClick}
|
||||||
|
>
|
||||||
|
<span className={twMerge(isActive && 'relative z-10', 'capitalize')}>
|
||||||
|
{extension ? formatExtensionsName(setting) : setting}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{isActive && (
|
||||||
|
<m.div
|
||||||
|
className="absolute inset-0 -left-3 h-full w-[calc(100%+24px)] rounded-md bg-primary/50"
|
||||||
|
layoutId="active-static-menu"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingItem
|
||||||
@ -1,55 +1,69 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { ScrollArea } from '@janhq/uikit'
|
import { ScrollArea } from '@janhq/uikit'
|
||||||
import { motion as m } from 'framer-motion'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
type Props = {
|
import { useAtomValue } from 'jotai'
|
||||||
activeMenu: string
|
|
||||||
onMenuClick: (menu: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingMenu: React.FC<Props> = ({ activeMenu, onMenuClick }) => {
|
import SettingItem from './SettingItem'
|
||||||
const [menus, setMenus] = useState<string[]>([])
|
|
||||||
|
import { extensionManager } from '@/extension'
|
||||||
|
import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
|
const SettingMenu: React.FC = () => {
|
||||||
|
const settingScreens = useAtomValue(janSettingScreenAtom)
|
||||||
|
const [extensionHasSettings, setExtensionHasSettings] = useState<string[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMenus([
|
const getAllSettings = async () => {
|
||||||
'My Models',
|
const activeExtensions = await extensionManager.getActive()
|
||||||
'My Settings',
|
const extensionsMenu: string[] = []
|
||||||
'Advanced Settings',
|
|
||||||
...(window.electronAPI ? ['Extensions'] : []),
|
for (const extension of activeExtensions) {
|
||||||
])
|
const extensionName = extension.name
|
||||||
|
if (!extensionName) continue
|
||||||
|
|
||||||
|
const baseExtension = extensionManager.get(extensionName)
|
||||||
|
if (!baseExtension) continue
|
||||||
|
|
||||||
|
if (typeof baseExtension.getSettings === 'function') {
|
||||||
|
const settings = await baseExtension.getSettings()
|
||||||
|
if (settings && settings.length > 0) {
|
||||||
|
extensionsMenu.push(extensionName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setExtensionHasSettings(extensionsMenu)
|
||||||
|
}
|
||||||
|
getAllSettings()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-64 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
|
<div className="flex h-full w-64 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
|
||||||
<ScrollArea className="h-full w-full">
|
<ScrollArea className="h-full w-full">
|
||||||
<div className="flex-shrink-0 px-6 py-4 font-medium">
|
<div className="flex-shrink-0 px-6 py-4 font-medium">
|
||||||
{menus.map((menu) => {
|
{settingScreens.map((settingScreen) => (
|
||||||
const isActive = activeMenu === menu
|
<SettingItem key={settingScreen} setting={settingScreen} />
|
||||||
return (
|
))}
|
||||||
<div
|
|
||||||
key={menu}
|
|
||||||
className="relative my-0.5 block cursor-pointer py-1.5"
|
|
||||||
onClick={() => onMenuClick(menu)}
|
|
||||||
>
|
|
||||||
<span className={twMerge(isActive && 'relative z-10')}>
|
|
||||||
{menu}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{isActive && (
|
{extensionHasSettings.length > 0 && (
|
||||||
<m.div
|
<div className="mb-2 mt-6">
|
||||||
className="absolute inset-0 -left-3 h-full w-[calc(100%+24px)] rounded-md bg-primary/50"
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
layoutId="active-static-menu"
|
Extensions
|
||||||
/>
|
</label>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
})}
|
|
||||||
|
{extensionHasSettings.map((extensionName: string) => (
|
||||||
|
<SettingItem
|
||||||
|
key={extensionName}
|
||||||
|
setting={extensionName}
|
||||||
|
extension={true}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingMenu
|
export default React.memo(SettingMenu)
|
||||||
|
|||||||
@ -1,51 +1,40 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import Advanced from '@/screens/Settings/Advanced'
|
import { useSetAtom } from 'jotai'
|
||||||
import AppearanceOptions from '@/screens/Settings/Appearance'
|
|
||||||
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
|
||||||
|
|
||||||
import Models from '@/screens/Settings/Models'
|
|
||||||
|
|
||||||
import { SUCCESS_SET_NEW_DESTINATION } from './Advanced/DataFolder'
|
import { SUCCESS_SET_NEW_DESTINATION } from './Advanced/DataFolder'
|
||||||
|
|
||||||
|
import SettingDetail from './SettingDetail'
|
||||||
import SettingMenu from './SettingMenu'
|
import SettingMenu from './SettingMenu'
|
||||||
|
|
||||||
const handleShowOptions = (menu: string) => {
|
import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
switch (menu) {
|
|
||||||
case 'Extensions':
|
|
||||||
return <ExtensionCatalog />
|
|
||||||
|
|
||||||
case 'My Settings':
|
export const SettingScreenList = [
|
||||||
return <AppearanceOptions />
|
'My Models',
|
||||||
|
'My Settings',
|
||||||
|
'Advanced Settings',
|
||||||
|
'Extensions',
|
||||||
|
] as const
|
||||||
|
|
||||||
case 'Advanced Settings':
|
export type SettingScreenTuple = typeof SettingScreenList
|
||||||
return <Advanced />
|
export type SettingScreen = SettingScreenTuple[number]
|
||||||
|
|
||||||
case 'My Models':
|
|
||||||
return <Models />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingsScreen: React.FC = () => {
|
const SettingsScreen: React.FC = () => {
|
||||||
const [activeStaticMenu, setActiveStaticMenu] = useState('My Models')
|
const setSelectedSettingScreen = useSetAtom(selectedSettingAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
|
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
|
||||||
setActiveStaticMenu('Advanced Settings')
|
setSelectedSettingScreen('Advanced Settings')
|
||||||
localStorage.removeItem(SUCCESS_SET_NEW_DESTINATION)
|
localStorage.removeItem(SUCCESS_SET_NEW_DESTINATION)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [setSelectedSettingScreen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex h-full bg-background"
|
|
||||||
data-testid="testid-setting-description"
|
data-testid="testid-setting-description"
|
||||||
|
className="flex h-full bg-background"
|
||||||
>
|
>
|
||||||
<SettingMenu
|
<SettingMenu />
|
||||||
activeMenu={activeStaticMenu}
|
<SettingDetail />
|
||||||
onMenuClick={setActiveStaticMenu}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{handleShowOptions(activeStaticMenu)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { Model } from '@janhq/core'
|
import { Model, SettingComponentProps } from '@janhq/core'
|
||||||
|
|
||||||
import { SettingComponentData } from '@/screens/Chat/ModelSetting/SettingComponent'
|
|
||||||
import { presetConfiguration } from '@/screens/Chat/ModelSetting/predefinedComponent'
|
import { presetConfiguration } from '@/screens/Chat/ModelSetting/predefinedComponent'
|
||||||
|
|
||||||
export const getConfigurationsData = (
|
export const getConfigurationsData = (
|
||||||
settings: object,
|
settings: object,
|
||||||
selectedModel?: Model
|
selectedModel?: Model
|
||||||
) => {
|
): SettingComponentProps[] => {
|
||||||
const componentData: SettingComponentData[] = []
|
const componentData: SettingComponentProps[] = []
|
||||||
|
|
||||||
Object.keys(settings).forEach((key: string) => {
|
Object.keys(settings).forEach((key: string) => {
|
||||||
const componentSetting = presetConfiguration[key]
|
const componentSetting = presetConfiguration[key]
|
||||||
@ -17,20 +16,20 @@ export const getConfigurationsData = (
|
|||||||
}
|
}
|
||||||
if ('slider' === componentSetting.controllerType) {
|
if ('slider' === componentSetting.controllerType) {
|
||||||
const value = Number(settings[key as keyof typeof settings])
|
const value = Number(settings[key as keyof typeof settings])
|
||||||
if ('value' in componentSetting.controllerData) {
|
if ('value' in componentSetting.controllerProps) {
|
||||||
componentSetting.controllerData.value = value
|
componentSetting.controllerProps.value = value
|
||||||
if ('max' in componentSetting.controllerData) {
|
if ('max' in componentSetting.controllerProps) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'max_tokens':
|
case 'max_tokens':
|
||||||
componentSetting.controllerData.max =
|
componentSetting.controllerProps.max =
|
||||||
selectedModel?.parameters.max_tokens ||
|
selectedModel?.parameters.max_tokens ||
|
||||||
componentSetting.controllerData.max ||
|
componentSetting.controllerProps.max ||
|
||||||
4096
|
4096
|
||||||
break
|
break
|
||||||
case 'ctx_len':
|
case 'ctx_len':
|
||||||
componentSetting.controllerData.max =
|
componentSetting.controllerProps.max =
|
||||||
selectedModel?.settings.ctx_len ||
|
selectedModel?.settings.ctx_len ||
|
||||||
componentSetting.controllerData.max ||
|
componentSetting.controllerProps.max ||
|
||||||
4096
|
4096
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -39,15 +38,15 @@ export const getConfigurationsData = (
|
|||||||
} else if ('input' === componentSetting.controllerType) {
|
} else if ('input' === componentSetting.controllerType) {
|
||||||
const value = settings[key as keyof typeof settings] as string
|
const value = settings[key as keyof typeof settings] as string
|
||||||
const placeholder = settings[key as keyof typeof settings] as string
|
const placeholder = settings[key as keyof typeof settings] as string
|
||||||
if ('value' in componentSetting.controllerData)
|
if ('value' in componentSetting.controllerProps)
|
||||||
componentSetting.controllerData.value = value
|
componentSetting.controllerProps.value = value
|
||||||
if ('placeholder' in componentSetting.controllerData)
|
if ('placeholder' in componentSetting.controllerProps)
|
||||||
componentSetting.controllerData.placeholder = placeholder
|
componentSetting.controllerProps.placeholder = placeholder
|
||||||
} else if ('checkbox' === componentSetting.controllerType) {
|
} else if ('checkbox' === componentSetting.controllerType) {
|
||||||
const checked = settings[key as keyof typeof settings] as boolean
|
const checked = settings[key as keyof typeof settings] as boolean
|
||||||
|
|
||||||
if ('checked' in componentSetting.controllerData)
|
if ('value' in componentSetting.controllerProps)
|
||||||
componentSetting.controllerData.checked = checked
|
componentSetting.controllerProps.value = checked
|
||||||
}
|
}
|
||||||
componentData.push(componentSetting)
|
componentData.push(componentSetting)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
import { ErrorCode } from '@janhq/core'
|
|
||||||
|
|
||||||
export const getErrorTitle = (
|
|
||||||
errorCode: ErrorCode,
|
|
||||||
errorMessage: string | undefined
|
|
||||||
) => {
|
|
||||||
switch (errorCode) {
|
|
||||||
case ErrorCode.Unknown:
|
|
||||||
return 'Apologies, something’s amiss!'
|
|
||||||
case ErrorCode.InvalidApiKey:
|
|
||||||
return 'Invalid API key. Please check your API key and try again.'
|
|
||||||
default:
|
|
||||||
return errorMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user