commit
f4641316b1
@ -53,11 +53,11 @@ export abstract class LocalOAIEngine extends OAIEngine {
|
||||
/**
|
||||
* Stops the model.
|
||||
*/
|
||||
override async unloadModel(model?: Model): Promise<void> {
|
||||
override async unloadModel(model?: Model) {
|
||||
if (model?.engine && model.engine?.toString() !== this.provider) return Promise.resolve()
|
||||
|
||||
this.loadedModel = undefined
|
||||
return executeOnMain(this.nodeModule, this.unloadModelFunctionName).then(() => {
|
||||
await executeOnMain(this.nodeModule, this.unloadModelFunctionName).then(() => {
|
||||
events.emit(ModelEvent.OnModelStopped, {})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
const DEFAULT_WIDTH = 1200
|
||||
const DEFAULT_MIN_WIDTH = 400
|
||||
const DEFAULT_HEIGHT = 800
|
||||
|
||||
export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||
width: DEFAULT_WIDTH,
|
||||
minWidth: DEFAULT_WIDTH,
|
||||
minWidth: DEFAULT_MIN_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
skipTaskbar: false,
|
||||
show: true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/assistant-extension",
|
||||
"productName": "Jan Assistant Extension",
|
||||
"productName": "Jan Assistant",
|
||||
"version": "1.0.1",
|
||||
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/conversational-extension",
|
||||
"productName": "Conversational Extension",
|
||||
"productName": "Conversational",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables conversations and state persistence via your filesystem",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/huggingface-extension",
|
||||
"productName": "HuggingFace Extension",
|
||||
"productName": "HuggingFace",
|
||||
"version": "1.0.0",
|
||||
"description": "Hugging Face extension for converting HF models to GGUF",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/inference-groq-extension",
|
||||
"productName": "Groq Inference Engine Extension",
|
||||
"productName": "Groq Inference Engine",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables fast Groq chat completion API calls",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/inference-mistral-extension",
|
||||
"productName": "Mistral AI Inference Engine Extension",
|
||||
"productName": "MistralAI Inference Engine",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables Mistral chat completion API calls",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1 +1 @@
|
||||
0.3.21
|
||||
0.3.16-hotfix
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@janhq/inference-nitro-extension",
|
||||
"productName": "Nitro Inference Engine Extension",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See https://nitro.jan.ai.\nUse this setting if you encounter errors related to **CUDA toolkit** during application execution.",
|
||||
"productName": "Nitro Inference Engine",
|
||||
"version": "1.0.1",
|
||||
"description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See https://nitro.jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.",
|
||||
"main": "dist/index.js",
|
||||
"node": "dist/node/index.cjs.js",
|
||||
"author": "Jan <service@jan.ai>",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"id": "command-r-34b",
|
||||
"object": "model",
|
||||
"name": "Command-R v01 34B Q4",
|
||||
"version": "1.0",
|
||||
"version": "1.1",
|
||||
"description": "C4AI Command-R developed by CohereAI is optimized for a variety of use cases including reasoning, summarization, and question answering.",
|
||||
"format": "gguf",
|
||||
"settings": {
|
||||
@ -27,7 +27,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"author": "CohereAI",
|
||||
"tags": ["34B", "Finetuned"],
|
||||
"tags": ["34B", "Finetuned", "Coming Soon", "Unavailable"],
|
||||
"size": 21500000000
|
||||
},
|
||||
"engine": "nitro"
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
{
|
||||
"sources": [
|
||||
{
|
||||
"filename": "wizardcoder-python-13b-v1.0.Q5_K_M.gguf",
|
||||
"url": "https://huggingface.co/TheBloke/WizardCoder-Python-13B-V1.0-GGUF/resolve/main/wizardcoder-python-13b-v1.0.Q5_K_M.gguf"
|
||||
"filename": "wizardcoder-python-13b-v1.0.Q4_K_M.gguf",
|
||||
"url": "https://huggingface.co/TheBloke/WizardCoder-Python-13B-V1.0-GGUF/resolve/main/wizardcoder-python-13b-v1.0.Q4_K_M.gguf"
|
||||
}
|
||||
],
|
||||
"id": "wizardcoder-13b",
|
||||
"object": "model",
|
||||
"name": "Wizard Coder Python 13B Q5",
|
||||
"version": "1.0",
|
||||
"name": "Wizard Coder Python 13B Q4",
|
||||
"version": "1.1",
|
||||
"description": "WizardCoder 13B is a Python coding model. This model demonstrate high proficiency in specific domains like coding and mathematics.",
|
||||
"format": "gguf",
|
||||
"settings": {
|
||||
"ctx_len": 4096,
|
||||
"prompt_template": "### Instruction:\n{prompt}\n### Response:",
|
||||
"llama_model_path": "wizardcoder-python-13b-v1.0.Q5_K_M.gguf"
|
||||
"llama_model_path": "wizardcoder-python-13b-v1.0.Q4_K_M.gguf"
|
||||
},
|
||||
"parameters": {
|
||||
"temperature": 0.7,
|
||||
|
||||
@ -102,7 +102,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine {
|
||||
return super.loadModel(model)
|
||||
}
|
||||
|
||||
override async unloadModel(model?: Model) {
|
||||
override async unloadModel(model?: Model): Promise<void> {
|
||||
if (model?.engine && model.engine !== this.provider) return
|
||||
|
||||
// stop the periocally health check
|
||||
|
||||
@ -323,14 +323,14 @@ async function killSubprocess(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
terminate(pid, function (err) {
|
||||
if (err) {
|
||||
return killRequest()
|
||||
killRequest().then(resolve).catch(reject)
|
||||
} else {
|
||||
return tcpPortUsed
|
||||
tcpPortUsed
|
||||
.waitUntilFree(PORT, NITRO_PORT_FREE_CHECK_INTERVAL, 5000)
|
||||
.then(() => resolve())
|
||||
.then(() => log(`[NITRO]::Debug: Nitro process is terminated`))
|
||||
.catch(() => {
|
||||
killRequest()
|
||||
killRequest().then(resolve).catch(reject)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/inference-openai-extension",
|
||||
"productName": "OpenAI Inference Engine Extension",
|
||||
"productName": "OpenAI Inference Engine",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables OpenAI chat completion API calls",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/inference-triton-trt-llm-extension",
|
||||
"productName": "Triton-TRT-LLM Inference Engine Extension",
|
||||
"productName": "Triton-TRT-LLM Inference Engine",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables Nvidia's TensorRT-LLM as an inference engine option",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/model-extension",
|
||||
"productName": "Model Management Extension",
|
||||
"productName": "Model Management",
|
||||
"version": "1.0.30",
|
||||
"description": "Model Management Extension provides model exploration and seamless downloads",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/monitoring-extension",
|
||||
"productName": "System Monitoring Extension",
|
||||
"productName": "System Monitoring",
|
||||
"version": "1.0.10",
|
||||
"description": "This extension provides system health and OS level data",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -49,7 +49,9 @@ const DEFAULT_SETTINGS: GpuSetting = {
|
||||
|
||||
export const getGpuConfig = async (): Promise<GpuSetting | undefined> => {
|
||||
if (process.platform === 'darwin') return undefined
|
||||
return JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||
if (existsSync(GPU_INFO_FILE))
|
||||
return JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||
return DEFAULT_SETTINGS
|
||||
}
|
||||
|
||||
export const getResourcesInfo = async (): Promise<ResourceInfo> => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@janhq/tensorrt-llm-extension",
|
||||
"productName": "TensorRT-LLM Inference Engine Extension",
|
||||
"productName": "TensorRT-LLM Inference Engine",
|
||||
"version": "0.0.3",
|
||||
"description": "This extension enables Nvidia's TensorRT-LLM for the fastest GPU acceleration. See the [setup guide](https://jan.ai/guides/providers/tensorrt-llm/) for next steps.",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -14,7 +14,7 @@ import { InfoIcon } from 'lucide-react'
|
||||
type Props = {
|
||||
name: string
|
||||
title: string
|
||||
enabled?: boolean
|
||||
disabled?: boolean
|
||||
description: string
|
||||
checked: boolean
|
||||
onValueChanged?: (e: string | number | boolean) => void
|
||||
@ -23,7 +23,7 @@ type Props = {
|
||||
const Checkbox: React.FC<Props> = ({
|
||||
title,
|
||||
checked,
|
||||
enabled = true,
|
||||
disabled = false,
|
||||
description,
|
||||
onValueChanged,
|
||||
}) => {
|
||||
@ -52,7 +52,7 @@ const Checkbox: React.FC<Props> = ({
|
||||
<Switch
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={!enabled}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -276,7 +276,7 @@ const DropdownListSidebar = ({
|
||||
{toGibibytes(x.metadata.size)}
|
||||
</span>
|
||||
{x.metadata.size && (
|
||||
<ModelLabel size={x.metadata.size} />
|
||||
<ModelLabel metadata={x.metadata} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,6 +16,8 @@ import CommandSearch from '@/containers/Layout/TopBar/CommandSearch'
|
||||
|
||||
import { showLeftSideBarAtom } from '@/containers/Providers/KeyListener'
|
||||
|
||||
import { toaster } from '@/containers/Toast'
|
||||
|
||||
import { MainViewState } from '@/constants/screens'
|
||||
|
||||
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||
@ -61,7 +63,11 @@ const TopBar = () => {
|
||||
|
||||
const onCreateConversationClick = async () => {
|
||||
if (assistants.length === 0) {
|
||||
alert('No assistant available')
|
||||
toaster({
|
||||
title: 'No assistant available.',
|
||||
description: `Could not create a new thread. Please add an assistant.`,
|
||||
type: 'error',
|
||||
})
|
||||
} else {
|
||||
requestCreateNewThread(assistants[0])
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import { InfoIcon } from 'lucide-react'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
enabled?: boolean
|
||||
disabled?: boolean
|
||||
name: string
|
||||
description: string
|
||||
placeholder: string
|
||||
@ -21,7 +21,7 @@ type Props = {
|
||||
|
||||
const ModelConfigInput: React.FC<Props> = ({
|
||||
title,
|
||||
enabled = true,
|
||||
disabled = false,
|
||||
value,
|
||||
description,
|
||||
placeholder,
|
||||
@ -48,7 +48,7 @@ const ModelConfigInput: React.FC<Props> = ({
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onValueChanged?.(e.target.value)}
|
||||
value={value}
|
||||
disabled={!enabled}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import { ModelMetadata } from '@janhq/core'
|
||||
import { Badge } from '@janhq/uikit'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
@ -19,10 +21,17 @@ import {
|
||||
} from '@/helpers/atoms/SystemBar.atom'
|
||||
|
||||
type Props = {
|
||||
size: number
|
||||
metadata: ModelMetadata
|
||||
}
|
||||
const UnsupportedModel = () => {
|
||||
return (
|
||||
<Badge className="space-x-1 rounded-md" themes="warning">
|
||||
<span>Coming Soon</span>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
const ModelLabel: React.FC<Props> = ({ size }) => {
|
||||
const ModelLabel: React.FC<Props> = ({ metadata }) => {
|
||||
const { activeModel } = useActiveModel()
|
||||
const totalRam = useAtomValue(totalRamAtom)
|
||||
const usedRam = useAtomValue(usedRamAtom)
|
||||
@ -52,7 +61,11 @@ const ModelLabel: React.FC<Props> = ({ size }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
return getLabel(size)
|
||||
return metadata.tags.includes('Coming Soon') ? (
|
||||
<UnsupportedModel />
|
||||
) : (
|
||||
getLabel(metadata.size ?? 0)
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ModelLabel)
|
||||
|
||||
@ -2,11 +2,14 @@
|
||||
|
||||
import { Fragment, ReactNode, useEffect } from 'react'
|
||||
|
||||
import { atom, useSetAtom } from 'jotai'
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai'
|
||||
|
||||
import { MainViewState } from '@/constants/screens'
|
||||
|
||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||
|
||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
@ -21,11 +24,19 @@ export default function KeyListener({ children }: Props) {
|
||||
const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom)
|
||||
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||
const showCommandSearchModal = useSetAtom(showCommandSearchModalAtom)
|
||||
const { requestCreateNewThread } = useCreateNewThread()
|
||||
const assistants = useAtomValue(assistantsAtom)
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const prefixKey = isMac ? e.metaKey : e.ctrlKey
|
||||
|
||||
if (e.key === 'n' && prefixKey) {
|
||||
requestCreateNewThread(assistants[0])
|
||||
setMainViewState(MainViewState.Thread)
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'b' && prefixKey) {
|
||||
setShowLeftSideBar((showLeftSideBar) => !showLeftSideBar)
|
||||
return
|
||||
@ -49,6 +60,8 @@ export default function KeyListener({ children }: Props) {
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
return () => document.removeEventListener('keydown', onKeyDown)
|
||||
}, [
|
||||
assistants,
|
||||
requestCreateNewThread,
|
||||
setMainViewState,
|
||||
setShowLeftSideBar,
|
||||
setShowSelectModelModal,
|
||||
|
||||
@ -10,6 +10,11 @@ import {
|
||||
} from '@janhq/uikit'
|
||||
|
||||
const availableShortcuts = [
|
||||
{
|
||||
combination: 'N',
|
||||
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
|
||||
description: 'Create a new thread',
|
||||
},
|
||||
{
|
||||
combination: 'E',
|
||||
modifierKeys: [isMac ? '⌘' : 'Ctrl'],
|
||||
|
||||
@ -17,7 +17,7 @@ import { useClickOutside } from '@/hooks/useClickOutside'
|
||||
type Props = {
|
||||
name: string
|
||||
title: string
|
||||
enabled: boolean
|
||||
disabled: boolean
|
||||
description: string
|
||||
min: number
|
||||
max: number
|
||||
@ -28,7 +28,7 @@ type Props = {
|
||||
|
||||
const SliderRightPanel: React.FC<Props> = ({
|
||||
title,
|
||||
enabled,
|
||||
disabled,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
@ -65,7 +65,7 @@ const SliderRightPanel: React.FC<Props> = ({
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
disabled={!enabled}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className="relative mt-2 flex items-center justify-between text-gray-400">
|
||||
<p className="text-sm">{min}</p>
|
||||
@ -80,8 +80,8 @@ const SliderRightPanel: React.FC<Props> = ({
|
||||
min={min}
|
||||
max={max}
|
||||
value={String(value)}
|
||||
disabled={disabled}
|
||||
textAlign="right"
|
||||
disabled={!enabled}
|
||||
onBlur={(e) => {
|
||||
if (Number(e.target.value) > Number(max)) {
|
||||
onValueChanged?.(Number(max))
|
||||
|
||||
@ -126,33 +126,27 @@ export function useActiveModel() {
|
||||
})
|
||||
}
|
||||
|
||||
const stopModel = useCallback(
|
||||
async (model?: Model) => {
|
||||
const stoppingModel = activeModel || model
|
||||
if (
|
||||
!stoppingModel ||
|
||||
(!model && stateModel.state === 'stop' && stateModel.loading)
|
||||
)
|
||||
return
|
||||
const stopModel = useCallback(async () => {
|
||||
const stoppingModel = activeModel || stateModel.model
|
||||
if (!stoppingModel || (stateModel.state === 'stop' && stateModel.loading))
|
||||
return
|
||||
|
||||
setStateModel({ state: 'stop', loading: true, model: stoppingModel })
|
||||
const engine = EngineManager.instance().get(stoppingModel.engine)
|
||||
return engine
|
||||
?.unloadModel(stoppingModel)
|
||||
.catch()
|
||||
.then(() => {
|
||||
setActiveModel(undefined)
|
||||
setStateModel({ state: 'start', loading: false, model: undefined })
|
||||
loadModelController?.abort()
|
||||
})
|
||||
},
|
||||
[activeModel, setActiveModel, setStateModel, stateModel]
|
||||
)
|
||||
setStateModel({ state: 'stop', loading: true, model: stoppingModel })
|
||||
const engine = EngineManager.instance().get(stoppingModel.engine)
|
||||
return engine
|
||||
?.unloadModel(stoppingModel)
|
||||
.catch()
|
||||
.then(() => {
|
||||
setActiveModel(undefined)
|
||||
setStateModel({ state: 'start', loading: false, model: undefined })
|
||||
loadModelController?.abort()
|
||||
})
|
||||
}, [activeModel, setActiveModel, setStateModel, stateModel])
|
||||
|
||||
const stopInference = useCallback(async () => {
|
||||
// Loading model
|
||||
if (stateModel.loading) {
|
||||
stopModel(stateModel.model)
|
||||
stopModel()
|
||||
return
|
||||
}
|
||||
if (!activeModel) return
|
||||
|
||||
@ -19,7 +19,7 @@ export const factoryResetStateAtom = atom(FactoryResetState.Idle)
|
||||
|
||||
export default function useFactoryReset() {
|
||||
const defaultJanDataFolder = useAtomValue(defaultJanDataFolderAtom)
|
||||
const { activeModel, stopModel } = useActiveModel()
|
||||
const { stopModel } = useActiveModel()
|
||||
const setFactoryResetState = useSetAtom(factoryResetStateAtom)
|
||||
|
||||
const resetAll = useCallback(
|
||||
@ -44,11 +44,9 @@ export default function useFactoryReset() {
|
||||
await window.core?.api?.updateAppConfiguration(configuration)
|
||||
}
|
||||
|
||||
if (activeModel) {
|
||||
setFactoryResetState(FactoryResetState.StoppingModel)
|
||||
await stopModel()
|
||||
await new Promise((resolve) => setTimeout(resolve, 4000))
|
||||
}
|
||||
setFactoryResetState(FactoryResetState.StoppingModel)
|
||||
await stopModel()
|
||||
await new Promise((resolve) => setTimeout(resolve, 4000))
|
||||
|
||||
setFactoryResetState(FactoryResetState.DeletingData)
|
||||
await fs.rm(janDataFolderPath)
|
||||
@ -59,7 +57,7 @@ export default function useFactoryReset() {
|
||||
|
||||
await window.core?.api?.relaunch()
|
||||
},
|
||||
[defaultJanDataFolder, activeModel, stopModel, setFactoryResetState]
|
||||
[defaultJanDataFolder, stopModel, setFactoryResetState]
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@ -1,54 +1,23 @@
|
||||
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 SettingComponentBuilder from '../../Chat/ModelSetting/SettingComponent'
|
||||
|
||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||
import {
|
||||
activeThreadAtom,
|
||||
engineParamsUpdateAtom,
|
||||
} from '@/helpers/atoms/Thread.atom'
|
||||
|
||||
type Props = {
|
||||
componentData: SettingComponentProps[]
|
||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
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]
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingComponentBuilder
|
||||
componentProps={componentData}
|
||||
enabled={!isLocalServerRunning}
|
||||
onValueUpdated={onValueChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const EngineSetting: React.FC<Props> = ({
|
||||
componentData,
|
||||
onValueChanged,
|
||||
disabled = false,
|
||||
}) => (
|
||||
<SettingComponentBuilder
|
||||
componentProps={componentData}
|
||||
disabled={disabled}
|
||||
onValueUpdated={onValueChanged}
|
||||
/>
|
||||
)
|
||||
|
||||
export default EngineSetting
|
||||
|
||||
@ -11,13 +11,13 @@ import SliderRightPanel from '@/containers/SliderRightPanel'
|
||||
|
||||
type Props = {
|
||||
componentProps: SettingComponentProps[]
|
||||
enabled?: boolean
|
||||
disabled?: boolean
|
||||
onValueUpdated: (key: string, value: string | number | boolean) => void
|
||||
}
|
||||
|
||||
const SettingComponent: React.FC<Props> = ({
|
||||
componentProps,
|
||||
enabled = true,
|
||||
disabled = false,
|
||||
onValueUpdated,
|
||||
}) => {
|
||||
const components = componentProps.map((data) => {
|
||||
@ -35,7 +35,7 @@ const SettingComponent: React.FC<Props> = ({
|
||||
step={step}
|
||||
value={value}
|
||||
name={data.key}
|
||||
enabled={enabled}
|
||||
disabled={disabled}
|
||||
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||
/>
|
||||
)
|
||||
@ -47,7 +47,7 @@ const SettingComponent: React.FC<Props> = ({
|
||||
return (
|
||||
<ModelConfigInput
|
||||
title={data.title}
|
||||
enabled={enabled}
|
||||
disabled={disabled}
|
||||
key={data.key}
|
||||
name={data.key}
|
||||
description={data.description}
|
||||
@ -63,7 +63,7 @@ const SettingComponent: React.FC<Props> = ({
|
||||
return (
|
||||
<Checkbox
|
||||
key={data.key}
|
||||
enabled={enabled}
|
||||
disabled={disabled}
|
||||
name={data.key}
|
||||
description={data.description}
|
||||
title={data.title}
|
||||
|
||||
@ -1,49 +1,25 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import { SettingComponentProps } from '@janhq/core/.'
|
||||
|
||||
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[]
|
||||
onValueChanged: (key: string, value: string | number | boolean) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
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]
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingComponentBuilder
|
||||
enabled={!isLocalServerRunning}
|
||||
componentProps={componentProps}
|
||||
onValueUpdated={onValueChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const ModelSetting: React.FC<Props> = ({
|
||||
componentProps,
|
||||
onValueChanged,
|
||||
disabled = false,
|
||||
}) => (
|
||||
<SettingComponentBuilder
|
||||
disabled={disabled}
|
||||
componentProps={componentProps}
|
||||
onValueUpdated={onValueChanged}
|
||||
/>
|
||||
)
|
||||
|
||||
export default React.memo(ModelSetting)
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useCallback, useMemo } from 'react'
|
||||
|
||||
import { Input, Textarea } from '@janhq/uikit'
|
||||
|
||||
import { atom, useAtomValue } from 'jotai'
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
@ -13,8 +13,11 @@ import DropdownListSidebar, {
|
||||
selectedModelAtom,
|
||||
} from '@/containers/DropdownListSidebar'
|
||||
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||
|
||||
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||
|
||||
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||
import { toRuntimeParams, toSettingParams } from '@/utils/modelParam'
|
||||
|
||||
@ -27,6 +30,7 @@ import PromptTemplateSetting from './PromptTemplateSetting'
|
||||
|
||||
import {
|
||||
activeThreadAtom,
|
||||
engineParamsUpdateAtom,
|
||||
getActiveThreadModelParamsAtom,
|
||||
} from '@/helpers/atoms/Thread.atom'
|
||||
|
||||
@ -39,6 +43,10 @@ const Sidebar: React.FC = () => {
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
const { updateThreadMetadata } = useCreateNewThread()
|
||||
|
||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||
const { stopModel } = useActiveModel()
|
||||
const { updateModelParameter } = useUpdateModelParameters()
|
||||
|
||||
const modelSettings = useMemo(() => {
|
||||
const modelRuntimeParams = toRuntimeParams(activeModelParams)
|
||||
|
||||
@ -96,6 +104,22 @@ const Sidebar: React.FC = () => {
|
||||
[activeThread, updateThreadMetadata]
|
||||
)
|
||||
|
||||
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 (
|
||||
<div
|
||||
className={twMerge(
|
||||
@ -170,7 +194,10 @@ const Sidebar: React.FC = () => {
|
||||
{modelSettings.length > 0 && (
|
||||
<CardSidebar title="Inference Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
<ModelSetting componentProps={modelSettings} />
|
||||
<ModelSetting
|
||||
componentProps={modelSettings}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
)}
|
||||
@ -188,7 +215,10 @@ const Sidebar: React.FC = () => {
|
||||
{engineSettings.length > 0 && (
|
||||
<CardSidebar title="Engine Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
<EngineSetting componentData={engineSettings} />
|
||||
<EngineSetting
|
||||
componentData={engineSettings}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
)}
|
||||
|
||||
@ -19,6 +19,10 @@ import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import ModalCancelDownload from '@/containers/ModalCancelDownload'
|
||||
|
||||
import ModelLabel from '@/containers/ModelLabel'
|
||||
|
||||
import { toaster } from '@/containers/Toast'
|
||||
|
||||
import { MainViewState } from '@/constants/screens'
|
||||
|
||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||
@ -47,22 +51,6 @@ type Props = {
|
||||
open: string
|
||||
}
|
||||
|
||||
const getLabel = (size: number, ram: number, unit: string = 'RAM') => {
|
||||
if (size * 1.25 >= ram) {
|
||||
return (
|
||||
<Badge className="rounded-md" themes="danger">
|
||||
Not enough {unit}
|
||||
</Badge>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Badge className="rounded-md" themes="success">
|
||||
Recommended
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
||||
const { downloadModel } = useDownloadModel()
|
||||
const downloadingModels = useAtomValue(getDownloadingModelAtom)
|
||||
@ -105,7 +93,11 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
||||
|
||||
const onUseModelClick = useCallback(async () => {
|
||||
if (assistants.length === 0) {
|
||||
alert('No assistant available')
|
||||
toaster({
|
||||
title: 'No assistant available.',
|
||||
description: `Could not use Model ${model.name} as no assistant is available.`,
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
await requestCreateNewThread(assistants[0], model)
|
||||
@ -164,11 +156,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
||||
<span className="mr-4 font-semibold text-muted-foreground">
|
||||
{toGibibytes(model.metadata.size)}
|
||||
</span>
|
||||
{getLabel(
|
||||
model.metadata.size,
|
||||
ram,
|
||||
settings?.run_mode === 'gpu' ? 'VRAM' : 'RAM'
|
||||
)}
|
||||
{<ModelLabel metadata={model.metadata} />}
|
||||
|
||||
{downloadButton}
|
||||
<ChevronDownIcon
|
||||
|
||||
@ -73,10 +73,15 @@ const LocalServerScreen = () => {
|
||||
const { startModel, stateModel } = useActiveModel()
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
|
||||
const modelEngineParams = toSettingParams(selectedModel?.settings)
|
||||
const modelRuntimeParams = toRuntimeParams(selectedModel?.settings)
|
||||
|
||||
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
|
||||
const [currentModelSettingParams, setCurrentModelSettingParams] = useState(
|
||||
toSettingParams(selectedModel?.settings)
|
||||
)
|
||||
|
||||
const componentDataEngineSetting = getConfigurationsData(
|
||||
currentModelSettingParams
|
||||
)
|
||||
const componentDataRuntimeSetting = getConfigurationsData(
|
||||
modelRuntimeParams,
|
||||
selectedModel
|
||||
@ -177,6 +182,16 @@ const LocalServerScreen = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onValueChanged = useCallback(
|
||||
(key: string, value: string | number | boolean) => {
|
||||
setCurrentModelSettingParams({
|
||||
...currentModelSettingParams,
|
||||
[key]: value,
|
||||
})
|
||||
},
|
||||
[currentModelSettingParams]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full" data-testid="local-server-testid">
|
||||
{/* Left SideBar */}
|
||||
@ -495,7 +510,11 @@ const LocalServerScreen = () => {
|
||||
<div className="mt-4">
|
||||
<CardSidebar title="Model Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
<ModelSetting componentProps={modelSettings} />
|
||||
<ModelSetting
|
||||
componentProps={modelSettings}
|
||||
disabled={serverEnabled}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
@ -505,7 +524,11 @@ const LocalServerScreen = () => {
|
||||
<div className="my-4">
|
||||
<CardSidebar title="Engine Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
<EngineSetting componentData={engineSettings} />
|
||||
<EngineSetting
|
||||
disabled={serverEnabled}
|
||||
componentData={engineSettings}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
|
||||
@ -26,8 +26,6 @@ import {
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { AlertTriangleIcon, AlertCircleIcon } from 'lucide-react'
|
||||
|
||||
import ShortcutModal from '@/containers/ShortcutModal'
|
||||
|
||||
import { snackbar, toaster } from '@/containers/Toast'
|
||||
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
@ -177,22 +175,7 @@ const Advanced = () => {
|
||||
|
||||
return (
|
||||
<ScrollArea className="px-4">
|
||||
<div className="block w-full">
|
||||
{/* Keyboard shortcut */}
|
||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-4 last:border-none">
|
||||
<div className="flex-shrink-0 space-y-1.5">
|
||||
<div className="flex gap-x-2">
|
||||
<h6 className="text-sm font-semibold capitalize">
|
||||
Keyboard Shortcuts
|
||||
</h6>
|
||||
</div>
|
||||
<p className="leading-relaxed">
|
||||
Shortcuts that you might find useful in Jan app.
|
||||
</p>
|
||||
</div>
|
||||
<ShortcutModal />
|
||||
</div>
|
||||
|
||||
<div className="block w-full py-4">
|
||||
{/* Experimental */}
|
||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||
<div className="flex-shrink-0 space-y-1.5">
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import ShortcutModal from '@/containers/ShortcutModal'
|
||||
|
||||
import ToggleAccent from '@/screens/Settings/Appearance/TogglePrimary'
|
||||
import ToggleTheme from '@/screens/Settings/Appearance/ToggleTheme'
|
||||
|
||||
@ -24,6 +26,20 @@ export default function AppearanceOptions() {
|
||||
</div>
|
||||
<ToggleAccent />
|
||||
</div>
|
||||
{/* Keyboard shortcut */}
|
||||
<div className="flex w-full items-start justify-between border-b border-border py-3 first:pt-4 last:border-none">
|
||||
<div className="flex-shrink-0 space-y-1.5">
|
||||
<div className="flex gap-x-2">
|
||||
<h6 className="text-sm font-semibold capitalize">
|
||||
Keyboard Shortcuts
|
||||
</h6>
|
||||
</div>
|
||||
<p className="leading-relaxed">
|
||||
Shortcuts that you might find useful in Jan app.
|
||||
</p>
|
||||
</div>
|
||||
<ShortcutModal />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ const ExtensionItem: React.FC<Props> = ({ item }) => {
|
||||
const description = marked.parse(item.description ?? '', { async: false })
|
||||
|
||||
return (
|
||||
<div className="mx-6 flex w-full items-start justify-between border-b border-border py-4 py-6 first:pt-4 last:border-none">
|
||||
<div className="mx-6 flex w-full items-start justify-between border-b border-border py-6 first:pt-4 last:border-none">
|
||||
<div className="flex-1 flex-shrink-0 space-y-1.5">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<h6 className="text-base font-bold">Additional Dependencies</h6>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
|
||||
import { Button, ScrollArea } from '@janhq/uikit'
|
||||
import { Marked, Renderer } from 'marked'
|
||||
|
||||
import Loader from '@/containers/Loader'
|
||||
|
||||
@ -88,9 +88,16 @@ const ExtensionCatalog = () => {
|
||||
{item.version}
|
||||
</h6>
|
||||
</div>
|
||||
<p className="whitespace-pre-wrap leading-relaxed ">
|
||||
{item.description}
|
||||
</p>
|
||||
{
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
__html: marked.parse(item.description ?? '', {
|
||||
async: false,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -130,4 +137,14 @@ const ExtensionCatalog = () => {
|
||||
)
|
||||
}
|
||||
|
||||
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'")
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default ExtensionCatalog
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user