Merge pull request #2725 from janhq/main

Sync main to dev (0.4.11)
This commit is contained in:
Louis 2024-04-15 15:19:10 +07:00 committed by GitHub
commit f4641316b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 247 additions and 213 deletions

View File

@ -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, {})
})
}

View File

@ -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,

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -1 +1 @@
0.3.21
0.3.16-hotfix

View File

@ -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>",

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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)
})
}
})

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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> => {

View File

@ -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",

View File

@ -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>
)

View File

@ -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>

View File

@ -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])
}

View File

@ -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>
)

View File

@ -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)

View File

@ -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,

View File

@ -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'],

View File

@ -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))

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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}

View File

@ -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)

View File

@ -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>
)}

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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