Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jan on web - experimental #4325

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/inference-cortex-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
* @returns
*/
private async clean(): Promise<any> {
// @ts-ignore
if (!window.electronAPI) return Promise.resolve()
return ky
.delete(`${CORTEX_API_URL}/processmanager/destroy`, {
timeout: 2000, // maximum 2 seconds
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"copy:assets": "cpx \"pre-install/*.tgz\" \"electron/pre-install/\" && cpx \"themes/**\" \"electron/themes\"",
"dev:electron": "yarn copy:assets && yarn workspace jan dev",
"dev:web": "yarn workspace @janhq/web dev",
"dev:web:standalone": "concurrently \"yarn workspace @janhq/web dev\" \"wait-on http://localhost:3000 && rsync -av --prune-empty-dirs --include '*/' --include 'dist/***' --include 'package.json' --include 'tsconfig.json' --exclude '*' ./extensions/ web/.next/static/extensions/\"",
"dev:server": "yarn workspace @janhq/server dev",
"dev": "concurrently -n \"NEXT,ELECTRON\" -c \"yellow,blue\" --kill-others \"yarn dev:web\" \"yarn dev:electron\"",
"build:server": "cd server && yarn build",
Expand Down
2 changes: 1 addition & 1 deletion web/extension/ExtensionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe('ExtensionManager', () => {
.spyOn(window.core.api, 'getActiveExtensions')
.mockResolvedValue([extension])
const activeExtensions = await manager.getActive()
expect(activeExtensions).toEqual([extension])
expect(activeExtensions.length).toBeGreaterThan(0)
})

it('should register all active extensions', async () => {
Expand Down
44 changes: 23 additions & 21 deletions web/extension/ExtensionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { AIEngine, BaseExtension, ExtensionTypeEnum } from '@janhq/core'

import WebExtensions from './../extensions.json'

import Extension from './Extension'

/**
Expand Down Expand Up @@ -99,21 +101,26 @@
* @returns An array of extensions.
*/
async getActive(): Promise<Extension[]> {
const res = await window.core?.api?.getActiveExtensions()
if (!res || !Array.isArray(res)) return []

const extensions: Extension[] = res.map(
(ext: any) =>
new Extension(
ext.url,
ext.name,
ext.productName,
ext.active,
ext.description,
ext.version
)
)
return extensions
if (window.electronAPI) {
const res = await window.core?.api?.getActiveExtensions()

Check warning on line 105 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

105 line is not covered with tests

if (!res || !Array.isArray(res)) return []

Check warning on line 107 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

107 line is not covered with tests

const extensions: Extension[] = res.map(

Check warning on line 109 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

109 line is not covered with tests
(ext: any) =>
new Extension(

Check warning on line 111 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

111 line is not covered with tests
ext.url,
ext.name,
ext.productName,
ext.active,
ext.description,
ext.version
)
)
return extensions

Check warning on line 120 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

120 line is not covered with tests
} else {
return WebExtensions
}
}

/**
Expand All @@ -123,20 +130,15 @@
*/
async activateExtension(extension: Extension) {
// Import class
const extensionUrl = window.electronAPI
? extension.url
: extension.url.replace(
'extension://',
`${window.core?.api?.baseApiUrl ?? ''}/extensions/`
)
const extensionUrl = extension.url
await import(/* webpackIgnore: true */ extensionUrl).then(

Check warning on line 134 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

133-134 lines are not covered with tests
(extensionClass) => {
// Register class if it has a default export
if (

Check warning on line 137 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

137 line is not covered with tests
typeof extensionClass.default === 'function' &&
extensionClass.default.prototype
) {
this.register(

Check warning on line 141 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

141 line is not covered with tests
extension.name,
new extensionClass.default(
extension.url,
Expand Down Expand Up @@ -171,15 +173,15 @@
* @returns {Promise.<Array.<Extension> | false>} extension as defined by the main process. Has property cancelled set to true if installation was cancelled in the main process.
*/
async install(extensions: any[]) {
if (typeof window === 'undefined') {
return

Check warning on line 177 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

176-177 lines are not covered with tests
}
const res = await window.core?.api?.installExtension(extensions)
if (res.cancelled) return false
return res.map(async (ext: any) => {
const extension = new Extension(ext.name, ext.url, ext.active)
await this.activateExtension(extension)
return extension

Check warning on line 184 in web/extension/ExtensionManager.ts

View workflow job for this annotation

GitHub Actions / coverage-check

179-184 lines are not covered with tests
})
}

Expand Down
182 changes: 182 additions & 0 deletions web/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
[
{
"_active": true,
"listeners": {},
"origin": "janhq-conversational-extension-1.0.0.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/conversational-extension",
"productName": "Conversational",
"version": "1.0.0",
"main": "dist/index.js",
"description": "This extension enables conversations and state persistence via your filesystem",
"url": "../../extensions/conversational-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-model-extension-1.0.35.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/model-extension",
"productName": "Model Management",
"version": "1.0.35",
"main": "dist/index.js",
"description": "Model Management Extension provides model exploration and seamless downloads",
"url": "../../extensions/model-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-anthropic-extension-1.0.3.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-anthropic-extension",
"productName": "Anthropic Inference Engine",
"version": "1.0.3",
"main": "dist/index.js",
"description": "This extension enables Anthropic chat completion API calls",
"url": "../../extensions/inference-anthropic-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-martian-extension-1.0.1.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-martian-extension",
"productName": "Martian Inference Engine",
"version": "1.0.1",
"main": "dist/index.js",
"description": "This extension enables Martian chat completion API calls",
"url": "../../extensions/inference-martian-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-nvidia-extension-1.0.1.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-nvidia-extension",
"productName": "NVIDIA NIM Inference Engine",
"version": "1.0.1",
"main": "dist/index.js",
"description": "This extension enables NVIDIA chat completion API calls",
"url": "../../extensions/inference-nvidia-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-openrouter-extension-1.0.0.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-openrouter-extension",
"productName": "OpenRouter Inference Engine",
"version": "1.0.0",
"main": "dist/index.js",
"description": "This extension enables Open Router chat completion API calls",
"url": "../../extensions/inference-openrouter-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-cohere-extension-1.0.0.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-cohere-extension",
"productName": "Cohere Inference Engine",
"version": "1.0.0",
"main": "dist/index.js",
"description": "This extension enables Cohere chat completion API calls",
"url": "../../extensions/inference-cohere-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-groq-extension-1.0.1.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-groq-extension",
"productName": "Groq Inference Engine",
"version": "1.0.1",
"main": "dist/index.js",
"description": "This extension enables fast Groq chat completion API calls",
"url": "../../extensions/inference-groq-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-openai-extension-1.0.5.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-openai-extension",
"productName": "OpenAI Inference Engine",
"version": "1.0.5",
"main": "dist/index.js",
"description": "This extension enables OpenAI chat completion API calls",
"url": "../../extensions/inference-openai-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-mistral-extension-1.0.1.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-mistral-extension",
"productName": "MistralAI Inference Engine",
"version": "1.0.1",
"main": "dist/index.js",
"description": "This extension enables Mistral chat completion API calls",
"url": "../../extensions/inference-mistral-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-triton-trt-llm-extension-1.0.0.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-triton-trt-llm-extension",
"productName": "Triton-TRT-LLM Inference Engine",
"version": "1.0.0",
"main": "dist/index.js",
"description": "This extension enables Nvidia's TensorRT-LLM as an inference engine option",
"url": "../../extensions/inference-triton-trtllm-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-monitoring-extension-1.0.10.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/monitoring-extension",
"productName": "System Monitoring",
"version": "1.0.10",
"main": "dist/index.js",
"description": "This extension provides system health and OS level data",
"url": "../../extensions/monitoring-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-assistant-extension-1.0.1.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/assistant-extension",
"productName": "Jan Assistant",
"version": "1.0.1",
"main": "dist/index.js",
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
"url": "../../extensions/assistant-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-tensorrt-llm-extension-0.0.3.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/tensorrt-llm-extension",
"productName": "TensorRT-LLM Inference Engine",
"version": "0.0.3",
"main": "dist/index.js",
"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.",
"url": "../../extensions/tensorrt-llm-extension/dist/index.js"
},
{
"_active": true,
"listeners": {},
"origin": "janhq-inference-cortex-extension-1.0.24.tgz",
"installOptions": { "version": false, "fullMetadata": true },
"name": "@janhq/inference-cortex-extension",
"productName": "Cortex Inference Engine",
"version": "1.0.24",
"main": "dist/index.js",
"description": "This extension embeds cortex.cpp, a lightweight inference engine written in C++. See https://jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.",
"url": "../../extensions/inference-cortex-extension/dist/index.js"
}
]
11 changes: 11 additions & 0 deletions web/hooks/useLoadTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
themeDataAtom,
themesOptionsAtom,
} from '@/helpers/atoms/Setting.atom'
import DefaultTheme from '@/theme.json'

type NativeThemeProps = 'light' | 'dark'

Expand Down Expand Up @@ -85,13 +86,23 @@
])

const configureTheme = useCallback(async () => {
if (!window.electronAPI) {
const theme = DefaultTheme as Theme
setThemeData(theme)
const variables = cssVars(theme.variables)
const headTag = document.getElementsByTagName('head')[0]
const styleTag = document.createElement('style')
styleTag.innerHTML = `:root {${variables}}`
headTag.appendChild(styleTag)
return
}
if (!themeData || !themeOptions) {
await getThemes()
} else {
applyTheme(themeData)
}
setNativeTheme(themeData?.nativeTheme as NativeThemeProps)
}, [themeData, themeOptions, getThemes, setNativeTheme])

Check warning on line 105 in web/hooks/useLoadTheme.ts

View workflow job for this annotation

GitHub Actions / test-on-macos

React Hook useCallback has a missing dependency: 'setThemeData'. Either include it or remove the dependency array

Check warning on line 105 in web/hooks/useLoadTheme.ts

View workflow job for this annotation

GitHub Actions / test-on-ubuntu

React Hook useCallback has a missing dependency: 'setThemeData'. Either include it or remove the dependency array

Check warning on line 105 in web/hooks/useLoadTheme.ts

View workflow job for this annotation

GitHub Actions / coverage-check

React Hook useCallback has a missing dependency: 'setThemeData'. Either include it or remove the dependency array

Check warning on line 105 in web/hooks/useLoadTheme.ts

View workflow job for this annotation

GitHub Actions / test-on-windows-pr

React Hook useCallback has a missing dependency: 'setThemeData'. Either include it or remove the dependency array

useEffect(() => {
configureTheme()
Expand Down
12 changes: 6 additions & 6 deletions web/hooks/useModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ const useModels = () => {
ModelManager.instance().models.get(e.id)?.metadata ?? e.metadata,
}))

const remoteModels = ModelManager.instance()
.models.values()
.toArray()
.filter((e) => !isLocalEngine(e.engine))
const remoteModels = Array.from(
ModelManager.instance().models.values()
).filter((e) => !isLocalEngine(e.engine))

const toUpdate = [
...localModels,
...remoteModels.filter(
Expand All @@ -68,7 +68,7 @@ const useModels = () => {
}

const getExtensionModels = () => {
const models = ModelManager.instance().models.values().toArray()
const models = Array.from(ModelManager.instance().models.values())
setExtensionModels(models)
}
// Fetch all data
Expand All @@ -79,7 +79,7 @@ const useModels = () => {
const reloadData = useDebouncedCallback(() => getData(), 300)

const updateStates = useCallback(() => {
const cachedModels = ModelManager.instance().models.values().toArray()
const cachedModels = Array.from(ModelManager.instance().models.values())
setDownloadedModels((downloadedModels) => [
...downloadedModels,
...cachedModels.filter(
Expand Down
25 changes: 16 additions & 9 deletions web/hooks/useSendChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,22 @@ export default function useSendChatMessage() {
setIsGeneratingResponse(true)

// Process message request with Assistants tools
const request = await ToolManager.instance().process(
requestBuilder.build(),
activeAssistantRef?.current.tools ?? []
)

// Request for inference
EngineManager.instance()
.get(requestBuilder.model?.engine ?? modelRequest.engine ?? '')
?.inference(request)
if (window.electronAPI) {
const request = await ToolManager.instance().process(
requestBuilder.build(),
activeAssistantRef?.current.tools ?? []
)

// Request for inference
EngineManager.instance()
.get(requestBuilder.model?.engine ?? modelRequest.engine ?? '')
?.inference(request)
} else {
// Request for inference
EngineManager.instance()
.get(requestBuilder.model?.engine ?? modelRequest.engine ?? '')
?.inference(requestBuilder.build())
}

// Reset states
setReloadModel(false)
Expand Down
18 changes: 5 additions & 13 deletions web/services/restService.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@


test('restAPI.baseApiUrl set correctly', () => {
const originalEnv = process.env.API_BASE_URL;
process.env.API_BASE_URL = 'http://test-api.com';

test('restAPI.openExternalUrl set correctly', () => {
// Re-import to get the updated value
jest.resetModules();
const { restAPI } = require('./restService');

expect(restAPI.baseApiUrl).toBe('http://test-api.com');
jest.resetModules()
const { restAPI } = require('./restService')

// Clean up
process.env.API_BASE_URL = originalEnv;
});
expect(restAPI.openExternalUrl).toBeDefined()
})
2 changes: 1 addition & 1 deletion web/services/restService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ export const restAPI = {
}, {}),
openExternalUrl,
// Jan Server URL
baseApiUrl: process.env.API_BASE_URL ?? API_BASE_URL,
baseApiUrl: undefined, //process.env.API_BASE_URL ?? API_BASE_URL,
pollingInterval: 5000,
}
Loading
Loading