Skip to content

Commit bb8986f

Browse files
JohnJohn
authored andcommitted
255: Cloud native
1 parent b19bda6 commit bb8986f

File tree

25 files changed

+576
-70
lines changed

25 files changed

+576
-70
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/node_modules

.github/workflows/jan-electron-build.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ jobs:
4343
env:
4444
CODE_SIGN_P12_BASE64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
4545

46-
- uses: apple-actions/import-codesign-certs@v2
47-
continue-on-error: true
48-
with:
49-
p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
50-
p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
46+
# - uses: apple-actions/import-codesign-certs@v2
47+
# continue-on-error: true
48+
# with:
49+
# p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
50+
# p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
5151

5252
- name: Install yarn dependencies
5353
run: |

Dockerfile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
FROM node:20-bullseye AS base
2+
3+
# 1. Install dependencies only when needed
4+
FROM base AS deps
5+
WORKDIR /app
6+
7+
# Install dependencies based on the preferred package manager
8+
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
9+
RUN yarn install
10+
11+
# # 2. Rebuild the source code only when needed
12+
FROM base AS builder
13+
WORKDIR /app
14+
COPY --from=deps /app/node_modules ./node_modules
15+
COPY . .
16+
# This will do the trick, use the corresponding env file for each environment.
17+
RUN yarn workspace server install
18+
RUN yarn server:prod
19+
20+
# 3. Production image, copy all the files and run next
21+
FROM base AS runner
22+
WORKDIR /app
23+
24+
ENV NODE_ENV=production
25+
26+
# RUN addgroup -g 1001 -S nodejs;
27+
COPY --from=builder /app/server/build ./
28+
29+
# Automatically leverage output traces to reduce image size
30+
# https://nextjs.org/docs/advanced-features/output-file-tracing
31+
COPY --from=builder /app/server/node_modules ./node_modules
32+
COPY --from=builder /app/server/package.json ./package.json
33+
34+
EXPOSE 4000 3928
35+
36+
ENV PORT 4000
37+
ENV APPDATA /app/data
38+
39+
CMD ["node", "main.js"]

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ yarn build
134134

135135
This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder.
136136

137+
### Run Jan as Web App
138+
```bash
139+
git clone https://github.com/janhq/jan
140+
cd jan
141+
yarn install
142+
yarn start:server
143+
```
144+
145+
### Run in container
146+
```bash
147+
git clone https://github.com/janhq/jan
148+
cd jan
149+
docker build --platform linux/x86_64 --progress=plain -t jan-server .
150+
docker run --platform linux/x86_64 --name jan-server -p4000:4000 -p3928:3928 -it jan-serve
151+
```
152+
137153
## Acknowledgements
138154

139155
Jan builds on top of other open-source projects:

electron/core/plugin-manager/execution/facade.js

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Plugin from "./Plugin";
99
import { register } from "./activation-manager";
10+
import plugins from "../../../../web/public/plugins/plugin.json"
1011

1112
/**
1213
* @typedef {Object.<string, any>} installOptions The {@link https://www.npmjs.com/package/pacote|pacote options}
@@ -65,19 +66,23 @@ export async function getActive() {
6566
return;
6667
}
6768
// eslint-disable-next-line no-undef
68-
const plgList = await window.pluggableElectronIpc.getActive();
69-
return plgList.map(
70-
(plugin) =>
71-
new Plugin(
72-
plugin.name,
73-
plugin.url,
74-
plugin.activationPoints,
75-
plugin.active,
76-
plugin.description,
77-
plugin.version,
78-
plugin.icon
79-
)
80-
);
69+
if(window.pluggableElectronIpc){
70+
// eslint-disable-next-line no-undef
71+
const plgList = await window.pluggableElectronIpc.getActive();
72+
return plgList.map(
73+
(plugin) =>
74+
new Plugin(
75+
plugin.name,
76+
plugin.url,
77+
plugin.activationPoints,
78+
plugin.active,
79+
plugin.description,
80+
plugin.version,
81+
plugin.icon
82+
)
83+
);
84+
}
85+
return Promise.resolve(plugins);
8186
}
8287

8388
/**
@@ -89,18 +94,18 @@ export async function registerActive() {
8994
if (typeof window === "undefined") {
9095
return;
9196
}
92-
// eslint-disable-next-line no-undef
93-
const plgList = await window.pluggableElectronIpc.getActive();
94-
plgList.forEach((plugin) =>
95-
register(
96-
new Plugin(
97-
plugin.name,
98-
plugin.url,
99-
plugin.activationPoints,
100-
plugin.active
97+
// eslint-disable-next-line no-undef
98+
const plgList = await getActive()
99+
plgList.forEach((plugin) =>
100+
register(
101+
new Plugin(
102+
plugin.name,
103+
plugin.url,
104+
plugin.activationPoints,
105+
plugin.active
106+
)
101107
)
102-
)
103-
);
108+
);
104109
}
105110

106111
/**

package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
"workspaces": {
55
"packages": [
66
"electron",
7-
"web"
7+
"web",
8+
"server"
89
],
910
"nohoist": [
1011
"electron",
1112
"electron/**",
1213
"web",
13-
"web/**"
14+
"web/**",
15+
"server",
16+
"server/**"
1417
]
1518
},
1619
"scripts": {
@@ -32,7 +35,11 @@
3235
"build:publish": "yarn build:web && yarn workspace jan build:publish",
3336
"build:publish-darwin": "yarn build:web && yarn workspace jan build:publish-darwin",
3437
"build:publish-win32": "yarn build:web && yarn workspace jan build:publish-win32",
35-
"build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux"
38+
"build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux",
39+
"build:web-plugins": "yarn build:web && yarn build:plugins && mkdir -p \"./web/out/plugins/data-plugin\" && cp \"./plugins/data-plugin/dist/esm/index.js\" \"./web/out/plugins/data-plugin\" && mkdir -p \"./web/out/plugins/inference-plugin\" && cp \"./plugins/inference-plugin/dist/index.js\" \"./web/out/plugins/inference-plugin\" && mkdir -p \"./web/out/plugins/model-management-plugin\" && cp \"./plugins/model-management-plugin/dist/index.js\" \"./web/out/plugins/model-management-plugin\" && mkdir -p \"./web/out/plugins/monitoring-plugin\" && cp \"./plugins/monitoring-plugin/dist/index.js\" \"./web/out/plugins/monitoring-plugin\"",
40+
"server:dev": "yarn build:web-plugins && cpx \"web/out/**\" \"server/renderer/\" && mkdir -p ./server/@janhq && cp -r ./plugins/* ./server/@janhq && yarn workspace server dev",
41+
"server:prod": "yarn workspace server build && yarn build:web-plugins && cpx \"web/out/**\" \"server/build/renderer/\" && mkdir -p ./server/build/@janhq && cp -r ./plugins/* ./server/build/@janhq",
42+
"start:server": "yarn server:prod && node server/build/main.js"
3643
},
3744
"devDependencies": {
3845
"concurrently": "^8.2.1",

plugins/data-plugin/module.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const dbs: Record<string, any> = {};
1616
*/
1717
function createCollection(name: string, schema?: { [key: string]: any }): Promise<void> {
1818
return new Promise<void>((resolve) => {
19-
const dbPath = path.join(app.getPath("userData"), "databases");
19+
const dbPath = path.join(appPath(), "databases");
2020
if (!fs.existsSync(dbPath)) fs.mkdirSync(dbPath);
2121
const db = new PouchDB(`${path.join(dbPath, name)}`);
2222
dbs[name] = db;
@@ -226,6 +226,13 @@ function findMany(
226226
.then((data) => data.docs); // Return documents
227227
}
228228

229+
function appPath() {
230+
if (app) {
231+
return app.getPath("userData");
232+
}
233+
return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
234+
}
235+
229236
module.exports = {
230237
createCollection,
231238
deleteCollection,

plugins/inference-plugin/module.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,22 @@ const initModel = (fileName) => {
2323
let binaryFolder = path.join(__dirname, "nitro"); // Current directory by default
2424
let binaryName;
2525

26-
if (process.platform === "win32") {
27-
// Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries
28-
binaryName = "nitro_start_windows.bat";
29-
} else if (process.platform === "darwin") {
30-
// Mac OS platform
31-
binaryName =
32-
process.arch === "arm64"
33-
? "nitro_mac_arm64"
34-
: "nitro_mac_intel";
35-
} else {
36-
// Linux
37-
// Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries
38-
binaryName = "nitro_start_linux.sh"; // For other platforms
39-
}
26+
if (process.platform === "win32") {
27+
// Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries
28+
binaryName = "nitro_start_windows.bat";
29+
} else if (process.platform === "darwin") {
30+
// Mac OS platform
31+
binaryName = process.arch === "arm64" ? "nitro_mac_arm64" : "nitro_mac_intel";
32+
} else {
33+
// Linux
34+
// Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries
35+
binaryName = "nitro_start_linux.sh"; // For other platforms
36+
}
4037

4138
const binaryPath = path.join(binaryFolder, binaryName);
4239

43-
// Execute the binary
44-
subprocess = spawn(binaryPath, { cwd: binaryFolder });
40+
// Execute the binary
41+
subprocess = spawn(binaryPath,["0.0.0.0", PORT], { cwd: binaryFolder });
4542

4643
// Handle subprocess output
4744
subprocess.stdout.on("data", (data) => {
@@ -61,7 +58,7 @@ const initModel = (fileName) => {
6158
})
6259
.then(() => tcpPortUsed.waitUntilUsed(PORT, 300, 30000))
6360
.then(() => {
64-
const llama_model_path = path.join(app.getPath("userData"), fileName);
61+
const llama_model_path = path.join(appPath(), fileName);
6562

6663
const config = {
6764
llama_model_path,
@@ -107,6 +104,13 @@ function killSubprocess() {
107104
}
108105
}
109106

107+
function appPath() {
108+
if (app) {
109+
return app.getPath("userData");
110+
}
111+
return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
112+
}
113+
110114
module.exports = {
111115
initModel,
112116
killSubprocess,

plugins/inference-plugin/nitro/nitro_start_linux.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
#!/bin/bash
44

55
# Attempt to run the nitro_linux_amd64_cuda file and if it fails, run nitro_linux_amd64
6-
./nitro_linux_amd64_cuda || (echo "nitro_linux_amd64_cuda encountered an error, attempting to run nitro_linux_amd64..." && ./nitro_linux_amd64)
6+
./nitro_linux_amd64_cuda "$@" || (echo "nitro_linux_amd64_cuda encountered an error, attempting to run nitro_linux_amd64..." && ./nitro_linux_amd64 "$@")

plugins/model-management-plugin/index.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,56 @@ import {
55
downloadFile,
66
deleteFile,
77
store,
8+
EventName,
9+
events
810
} from "@janhq/core";
911
import { parseToModel } from "./helper";
1012

11-
const downloadModel = (product) =>
13+
const downloadModel = (product) => {
1214
downloadFile(product.downloadUrl, product.fileName);
15+
checkDownloadProgress(product.fileName);
16+
}
17+
18+
async function checkDownloadProgress(fileName: string) {
19+
if (typeof window !== "undefined" && typeof (window as any).electronAPI === "undefined") {
20+
const intervalId = setInterval(() => {
21+
fetchDownloadProgress(fileName, intervalId);
22+
}, 3000);
23+
}
24+
}
25+
26+
async function fetchDownloadProgress(fileName: string, intervalId: NodeJS.Timeout): Promise<string> {
27+
const response = await fetch("/api/v1/downloadProgress", {
28+
method: 'POST',
29+
body: JSON.stringify({ fileName: fileName }),
30+
headers: { 'Content-Type': 'application/json', 'Authorization': '' }
31+
});
32+
33+
if (!response.ok) {
34+
events.emit(EventName.OnDownloadError, null);
35+
clearInterval(intervalId);
36+
return;
37+
}
38+
const json = await response.json();
39+
if (isEmptyObject(json)) {
40+
if (!fileName && intervalId) {
41+
clearInterval(intervalId);
42+
}
43+
return Promise.resolve("");
44+
}
45+
if (json.success === true) {
46+
events.emit(EventName.OnDownloadSuccess, json);
47+
clearInterval(intervalId);
48+
return Promise.resolve("");
49+
} else {
50+
events.emit(EventName.OnDownloadUpdate, json);
51+
return Promise.resolve(json.fileName);
52+
}
53+
}
54+
55+
function isEmptyObject(ojb: any): boolean {
56+
return Object.keys(ojb).length === 0;
57+
}
1358

1459
const deleteModel = (path) => deleteFile(path);
1560

@@ -87,6 +132,7 @@ function getModelById(modelId: string): Promise<any> {
87132

88133
function onStart() {
89134
store.createCollection("models", {});
135+
fetchDownloadProgress(null, null).then((fileName: string) => fileName && checkDownloadProgress(fileName));
90136
}
91137

92138
// Register all the above functions and objects with the relevant extension points

0 commit comments

Comments
 (0)