refactor: file prefix replace utils & add unit test (#1676)
* refactor: file prefix replace utils * chore: add unit tests for core module
This commit is contained in:
parent
fc3a5c9e01
commit
99d083d84a
1
Makefile
1
Makefile
@ -39,6 +39,7 @@ lint: check-file-counts
|
|||||||
# Testing
|
# Testing
|
||||||
test: lint
|
test: lint
|
||||||
yarn build:test
|
yarn build:test
|
||||||
|
yarn test:unit
|
||||||
yarn test
|
yarn test
|
||||||
|
|
||||||
# Builds and publishes the app
|
# Builds and publishes the app
|
||||||
|
|||||||
3
core/.gitignore
vendored
3
core/.gitignore
vendored
@ -6,7 +6,4 @@ coverage
|
|||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
dist
|
dist
|
||||||
compiled
|
|
||||||
.awcache
|
|
||||||
.rpt2_cache
|
|
||||||
docs
|
docs
|
||||||
|
|||||||
7
core/jest.config.js
Normal file
7
core/jest.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
moduleNameMapper: {
|
||||||
|
'@/(.*)': '<rootDir>/src/$1',
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -12,7 +12,8 @@
|
|||||||
"module": "dist/core.es5.js",
|
"module": "dist/core.es5.js",
|
||||||
"typings": "dist/types/index.d.ts",
|
"typings": "dist/types/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"types"
|
||||||
],
|
],
|
||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
"exports": {
|
"exports": {
|
||||||
@ -38,18 +39,23 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
|
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
|
||||||
|
"test": "jest",
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||||
"start": "rollup -c rollup.config.ts -w"
|
"start": "rollup -c rollup.config.ts -w"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"jest": "^25.4.0",
|
||||||
|
"@types/jest": "^29.5.11",
|
||||||
"@types/node": "^12.0.2",
|
"@types/node": "^12.0.2",
|
||||||
|
"eslint-plugin-jest": "^23.8.2",
|
||||||
"rollup": "^2.38.5",
|
"rollup": "^2.38.5",
|
||||||
"rollup-plugin-commonjs": "^9.1.8",
|
"rollup-plugin-commonjs": "^9.1.8",
|
||||||
"rollup-plugin-json": "^3.1.0",
|
"rollup-plugin-json": "^3.1.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||||
"rollup-plugin-typescript2": "^0.36.0",
|
"rollup-plugin-typescript2": "^0.36.0",
|
||||||
|
"ts-jest": "^26.1.1",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,58 +1,58 @@
|
|||||||
import { DownloadRoute } from '../../../api'
|
import { DownloadRoute } from "../../../api";
|
||||||
import { join } from 'path'
|
import { join } from "path";
|
||||||
import { userSpacePath } from '../../extension/manager'
|
import { userSpacePath } from "../../extension/manager";
|
||||||
import { DownloadManager } from '../../download'
|
import { DownloadManager } from "../../download";
|
||||||
import { HttpServer } from '../HttpServer'
|
import { HttpServer } from "../HttpServer";
|
||||||
import { createWriteStream } from 'fs'
|
import { createWriteStream } from "fs";
|
||||||
|
import { normalizeFilePath } from "../../path";
|
||||||
|
|
||||||
export const downloadRouter = async (app: HttpServer) => {
|
export const downloadRouter = async (app: HttpServer) => {
|
||||||
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
||||||
const strictSSL = !(req.query.ignoreSSL === 'true');
|
const strictSSL = !(req.query.ignoreSSL === "true");
|
||||||
const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined;
|
const proxy = req.query.proxy?.startsWith("http") ? req.query.proxy : undefined;
|
||||||
const body = JSON.parse(req.body as any)
|
const body = JSON.parse(req.body as any);
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
const normalizedArgs = body.map((arg: any) => {
|
||||||
if (typeof arg === 'string' && arg.includes('file:/')) {
|
if (typeof arg === "string") {
|
||||||
return join(userSpacePath, arg.replace('file:/', ''))
|
return join(userSpacePath, normalizeFilePath(arg));
|
||||||
}
|
}
|
||||||
return arg
|
return arg;
|
||||||
})
|
});
|
||||||
|
|
||||||
const localPath = normalizedArgs[1]
|
const localPath = normalizedArgs[1];
|
||||||
const fileName = localPath.split('/').pop() ?? ''
|
const fileName = localPath.split("/").pop() ?? "";
|
||||||
|
|
||||||
const request = require('request')
|
const request = require("request");
|
||||||
const progress = require('request-progress')
|
const progress = require("request-progress");
|
||||||
|
|
||||||
const rq = request({ url: normalizedArgs[0], strictSSL, proxy })
|
const rq = request({ url: normalizedArgs[0], strictSSL, proxy });
|
||||||
progress(rq, {})
|
progress(rq, {})
|
||||||
.on('progress', function (state: any) {
|
.on("progress", function (state: any) {
|
||||||
console.log('download onProgress', state)
|
console.log("download onProgress", state);
|
||||||
})
|
})
|
||||||
.on('error', function (err: Error) {
|
.on("error", function (err: Error) {
|
||||||
console.log('download onError', err)
|
console.log("download onError", err);
|
||||||
})
|
})
|
||||||
.on('end', function () {
|
.on("end", function () {
|
||||||
console.log('download onEnd')
|
console.log("download onEnd");
|
||||||
})
|
})
|
||||||
.pipe(createWriteStream(normalizedArgs[1]))
|
.pipe(createWriteStream(normalizedArgs[1]));
|
||||||
|
|
||||||
DownloadManager.instance.setRequest(fileName, rq)
|
DownloadManager.instance.setRequest(fileName, rq);
|
||||||
})
|
});
|
||||||
|
|
||||||
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
||||||
const body = JSON.parse(req.body as any)
|
const body = JSON.parse(req.body as any);
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
const normalizedArgs = body.map((arg: any) => {
|
||||||
if (typeof arg === 'string' && arg.includes('file:/')) {
|
if (typeof arg === "string") {
|
||||||
return join(userSpacePath, arg.replace('file:/', ''))
|
return join(userSpacePath, normalizeFilePath(arg));
|
||||||
}
|
}
|
||||||
return arg
|
return arg;
|
||||||
})
|
});
|
||||||
|
|
||||||
const localPath = normalizedArgs[0]
|
const localPath = normalizedArgs[0];
|
||||||
const fileName = localPath.split('/').pop() ?? ''
|
const fileName = localPath.split("/").pop() ?? "";
|
||||||
console.debug('fileName', fileName)
|
const rq = DownloadManager.instance.networkRequests[fileName];
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
DownloadManager.instance.networkRequests[fileName] = undefined;
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
rq?.abort();
|
||||||
rq?.abort()
|
});
|
||||||
})
|
};
|
||||||
}
|
|
||||||
|
|||||||
@ -6,3 +6,4 @@ export * from './download'
|
|||||||
export * from './module'
|
export * from './module'
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export * from './log'
|
export * from './log'
|
||||||
|
export * from './path'
|
||||||
|
|||||||
9
core/src/node/path.ts
Normal file
9
core/src/node/path.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Normalize file path
|
||||||
|
* Remove all file protocol prefix
|
||||||
|
* @param path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function normalizeFilePath(path: string): string {
|
||||||
|
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, "$2");
|
||||||
|
}
|
||||||
12
core/tests/node/path.test.ts
Normal file
12
core/tests/node/path.test.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { normalizeFilePath } from "../../src/node/path";
|
||||||
|
|
||||||
|
describe("Test file normalize", () => {
|
||||||
|
test("returns no file protocol prefix on Unix", async () => {
|
||||||
|
expect(normalizeFilePath("file://test.txt")).toBe("test.txt");
|
||||||
|
expect(normalizeFilePath("file:/test.txt")).toBe("test.txt");
|
||||||
|
});
|
||||||
|
test("returns no file protocol prefix on Windows", async () => {
|
||||||
|
expect(normalizeFilePath("file:\\\\test.txt")).toBe("test.txt");
|
||||||
|
expect(normalizeFilePath("file:\\test.txt")).toBe("test.txt");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -13,7 +13,7 @@
|
|||||||
"declarationDir": "dist/types",
|
"declarationDir": "dist/types",
|
||||||
"outDir": "dist/lib",
|
"outDir": "dist/lib",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"typeRoots": ["node_modules/@types"]
|
"types": ["@types/jest"]
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import request from 'request'
|
|||||||
import { createWriteStream, renameSync } from 'fs'
|
import { createWriteStream, renameSync } from 'fs'
|
||||||
import { DownloadEvent, DownloadRoute } from '@janhq/core'
|
import { DownloadEvent, DownloadRoute } from '@janhq/core'
|
||||||
const progress = require('request-progress')
|
const progress = require('request-progress')
|
||||||
import { DownloadManager } from '@janhq/core/node'
|
import { DownloadManager, normalizeFilePath } from '@janhq/core/node'
|
||||||
|
|
||||||
export function handleDownloaderIPCs() {
|
export function handleDownloaderIPCs() {
|
||||||
/**
|
/**
|
||||||
@ -54,15 +54,16 @@ export function handleDownloaderIPCs() {
|
|||||||
* @param url - The URL to download the file from.
|
* @param url - The URL to download the file from.
|
||||||
* @param fileName - The name to give the downloaded file.
|
* @param fileName - The name to give the downloaded file.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle(DownloadRoute.downloadFile, async (_event, url, fileName, network) => {
|
ipcMain.handle(
|
||||||
const strictSSL = !network?.ignoreSSL;
|
DownloadRoute.downloadFile,
|
||||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined;
|
async (_event, url, fileName, network) => {
|
||||||
|
const strictSSL = !network?.ignoreSSL
|
||||||
|
const proxy = network?.proxy?.startsWith('http')
|
||||||
|
? network.proxy
|
||||||
|
: undefined
|
||||||
const userDataPath = join(app.getPath('home'), 'jan')
|
const userDataPath = join(app.getPath('home'), 'jan')
|
||||||
if (
|
if (typeof fileName === 'string') {
|
||||||
typeof fileName === 'string' &&
|
fileName = normalizeFilePath(fileName)
|
||||||
(fileName.includes('file:/') || fileName.includes('file:\\'))
|
|
||||||
) {
|
|
||||||
fileName = fileName.replace('file:/', '').replace('file:\\', '')
|
|
||||||
}
|
}
|
||||||
const destination = resolve(userDataPath, fileName)
|
const destination = resolve(userDataPath, fileName)
|
||||||
const rq = request({ url, strictSSL, proxy })
|
const rq = request({ url, strictSSL, proxy })
|
||||||
@ -115,5 +116,6 @@ export function handleDownloaderIPCs() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pipe(createWriteStream(downloadingTempFile))
|
.pipe(createWriteStream(downloadingTempFile))
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { userSpacePath, getResourcePath } from './../utils/path'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { FileStat } from '@janhq/core'
|
import { FileStat } from '@janhq/core'
|
||||||
|
import { normalizeFilePath } from '@janhq/core/node'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file system extensions operations.
|
* Handles file system extensions operations.
|
||||||
@ -42,11 +43,7 @@ export function handleFileMangerIPCs() {
|
|||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
FileManagerRoute.fileStat,
|
FileManagerRoute.fileStat,
|
||||||
async (_event, path: string): Promise<FileStat | undefined> => {
|
async (_event, path: string): Promise<FileStat | undefined> => {
|
||||||
const normalizedPath = path
|
const normalizedPath = normalizeFilePath(path)
|
||||||
.replace(`file://`, '')
|
|
||||||
.replace(`file:/`, '')
|
|
||||||
.replace(`file:\\\\`, '')
|
|
||||||
.replace(`file:\\`, '')
|
|
||||||
|
|
||||||
const fullPath = join(userSpacePath, normalizedPath)
|
const fullPath = join(userSpacePath, normalizedPath)
|
||||||
const isExist = fs.existsSync(fullPath)
|
const isExist = fs.existsSync(fullPath)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ipcMain } from 'electron'
|
|||||||
import { FileSystemRoute } from '@janhq/core'
|
import { FileSystemRoute } from '@janhq/core'
|
||||||
import { userSpacePath } from '../utils/path'
|
import { userSpacePath } from '../utils/path'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { normalizeFilePath } from '@janhq/core/node'
|
||||||
/**
|
/**
|
||||||
* Handles file system operations.
|
* Handles file system operations.
|
||||||
*/
|
*/
|
||||||
@ -15,14 +16,7 @@ export function handleFsIPCs() {
|
|||||||
...args.map((arg) =>
|
...args.map((arg) =>
|
||||||
typeof arg === 'string' &&
|
typeof arg === 'string' &&
|
||||||
(arg.includes(`file:/`) || arg.includes(`file:\\`))
|
(arg.includes(`file:/`) || arg.includes(`file:\\`))
|
||||||
? join(
|
? join(userSpacePath, normalizeFilePath(arg))
|
||||||
userSpacePath,
|
|
||||||
arg
|
|
||||||
.replace(`file://`, '')
|
|
||||||
.replace(`file:/`, '')
|
|
||||||
.replace(`file:\\\\`, '')
|
|
||||||
.replace(`file:\\`, '')
|
|
||||||
)
|
|
||||||
: arg
|
: arg
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,19 +11,15 @@
|
|||||||
],
|
],
|
||||||
"nohoist": [
|
"nohoist": [
|
||||||
"uikit",
|
"uikit",
|
||||||
"uikit/*",
|
|
||||||
"core",
|
"core",
|
||||||
"core/*",
|
|
||||||
"electron",
|
"electron",
|
||||||
"electron/**",
|
|
||||||
"web",
|
"web",
|
||||||
"web/**",
|
"server"
|
||||||
"server",
|
|
||||||
"server/**"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
||||||
|
"test:unit": "yarn workspace @janhq/core test",
|
||||||
"test": "yarn workspace jan test:e2e",
|
"test": "yarn workspace jan test:e2e",
|
||||||
"copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"",
|
"copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"",
|
||||||
"dev:electron": "yarn copy:assets && yarn workspace jan dev",
|
"dev:electron": "yarn copy:assets && yarn workspace jan dev",
|
||||||
|
|||||||
@ -29,7 +29,8 @@ export default function FeatureToggleWrapper({
|
|||||||
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
||||||
const IGNORE_SSL = 'ignoreSSLFeature'
|
const IGNORE_SSL = 'ignoreSSLFeature'
|
||||||
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
||||||
const [experimentalFeature, directSetExperimentalFeature] = useState<boolean>(false)
|
const [experimentalFeature, directSetExperimentalFeature] =
|
||||||
|
useState<boolean>(false)
|
||||||
const [ignoreSSL, directSetIgnoreSSL] = useState<boolean>(false)
|
const [ignoreSSL, directSetIgnoreSSL] = useState<boolean>(false)
|
||||||
const [proxy, directSetProxy] = useState<string>('')
|
const [proxy, directSetProxy] = useState<string>('')
|
||||||
|
|
||||||
@ -37,12 +38,8 @@ export default function FeatureToggleWrapper({
|
|||||||
directSetExperimentalFeature(
|
directSetExperimentalFeature(
|
||||||
localStorage.getItem(EXPERIMENTAL_FEATURE) === 'true'
|
localStorage.getItem(EXPERIMENTAL_FEATURE) === 'true'
|
||||||
)
|
)
|
||||||
directSetIgnoreSSL(
|
directSetIgnoreSSL(localStorage.getItem(IGNORE_SSL) === 'true')
|
||||||
localStorage.getItem(IGNORE_SSL) === 'true'
|
directSetProxy(localStorage.getItem(HTTPS_PROXY_FEATURE) ?? '')
|
||||||
)
|
|
||||||
directSetProxy(
|
|
||||||
localStorage.getItem(HTTPS_PROXY_FEATURE) ?? ""
|
|
||||||
)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setExperimentalFeature = (on: boolean) => {
|
const setExperimentalFeature = (on: boolean) => {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useContext } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
ExtensionType,
|
ExtensionType,
|
||||||
@ -8,14 +10,14 @@ import {
|
|||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
||||||
|
|
||||||
import { modelBinFileName } from '@/utils/model'
|
import { modelBinFileName } from '@/utils/model'
|
||||||
|
|
||||||
import { useDownloadState } from './useDownloadState'
|
import { useDownloadState } from './useDownloadState'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
import { addNewDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
import { addNewDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
||||||
import { useContext } from 'react'
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
export default function useDownloadModel() {
|
export default function useDownloadModel() {
|
||||||
const { ignoreSSL, proxy } = useContext(FeatureToggleContext)
|
const { ignoreSSL, proxy } = useContext(FeatureToggleContext)
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx}\"",
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx}\"",
|
||||||
"compile": "tsc --noEmit -p . --pretty"
|
"compile": "tsc --noEmit -p . --pretty"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useContext, useEffect, useState, useCallback, ChangeEvent } from 'react'
|
import {
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
ChangeEvent,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
import { fs } from '@janhq/core'
|
import { fs } from '@janhq/core'
|
||||||
import {
|
import { Switch, Button, Input } from '@janhq/uikit'
|
||||||
Switch,
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalTitle,
|
|
||||||
ModalTrigger,
|
|
||||||
} from '@janhq/uikit'
|
|
||||||
|
|
||||||
import ShortcutModal from '@/containers/ShortcutModal'
|
import ShortcutModal from '@/containers/ShortcutModal'
|
||||||
|
|
||||||
@ -24,22 +21,30 @@ import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|||||||
import { useSettings } from '@/hooks/useSettings'
|
import { useSettings } from '@/hooks/useSettings'
|
||||||
|
|
||||||
const Advanced = () => {
|
const Advanced = () => {
|
||||||
const { experimentalFeature, setExperimentalFeature, ignoreSSL, setIgnoreSSL, proxy, setProxy } =
|
const {
|
||||||
useContext(FeatureToggleContext)
|
experimentalFeature,
|
||||||
|
setExperimentalFeature,
|
||||||
|
ignoreSSL,
|
||||||
|
setIgnoreSSL,
|
||||||
|
proxy,
|
||||||
|
setProxy,
|
||||||
|
} = useContext(FeatureToggleContext)
|
||||||
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
||||||
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
||||||
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
||||||
useSettings()
|
useSettings()
|
||||||
const onProxyChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
const onProxyChange = useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.target.value || ''
|
const value = event.target.value || ''
|
||||||
setPartialProxy(value)
|
setPartialProxy(value)
|
||||||
if (value.trim().startsWith('http')) {
|
if (value.trim().startsWith('http')) {
|
||||||
setProxy(value.trim())
|
setProxy(value.trim())
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
setProxy('')
|
setProxy('')
|
||||||
}
|
}
|
||||||
}, [setPartialProxy, setProxy])
|
},
|
||||||
|
[setPartialProxy, setProxy]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
readSettings().then((settings) => {
|
readSettings().then((settings) => {
|
||||||
@ -115,15 +120,14 @@ const Advanced = () => {
|
|||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
<h6 className="text-sm font-semibold capitalize">HTTPS Proxy</h6>
|
||||||
HTTPS Proxy
|
|
||||||
</h6>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="whitespace-pre-wrap leading-relaxed">
|
||||||
Specify the HTTPS proxy or leave blank (proxy auto-configuration and SOCKS not supported).
|
Specify the HTTPS proxy or leave blank (proxy auto-configuration and
|
||||||
|
SOCKS not supported).
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
placeholder={"http://<user>:<password>@<domain or IP>:<port>"}
|
placeholder={'http://<user>:<password>@<domain or IP>:<port>'}
|
||||||
value={partialProxy}
|
value={partialProxy}
|
||||||
onChange={onProxyChange}
|
onChange={onProxyChange}
|
||||||
/>
|
/>
|
||||||
@ -138,7 +142,8 @@ const Advanced = () => {
|
|||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="whitespace-pre-wrap leading-relaxed">
|
||||||
Allow self-signed or unverified certificates - may be required for certain proxies.
|
Allow self-signed or unverified certificates - may be required for
|
||||||
|
certain proxies.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
type Props = {
|
|
||||||
extensionName: string
|
|
||||||
preferenceValues: any
|
|
||||||
preferenceItems: any
|
|
||||||
}
|
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
Input,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
Button,
|
|
||||||
} from '@janhq/uikit'
|
|
||||||
|
|
||||||
import * as z from 'zod'
|
|
||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
|
||||||
|
|
||||||
import { formatExtensionsName } from '@/utils/converter'
|
|
||||||
|
|
||||||
const PreferenceExtensions = (props: Props) => {
|
|
||||||
const { extensionName, preferenceValues, preferenceItems } = props
|
|
||||||
|
|
||||||
const FormSchema = z.record(
|
|
||||||
z
|
|
||||||
.string({ required_error: 'Field is Required' })
|
|
||||||
.min(1, { message: 'Field is Required' })
|
|
||||||
)
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
defaultValues: preferenceValues.reduce(
|
|
||||||
(obj: any, item: { key: any; value: any }) =>
|
|
||||||
Object.assign(obj, { [item.key]: item.value }),
|
|
||||||
{}
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof FormSchema>) => {
|
|
||||||
for (const [key, value] of Object.entries(values)) {
|
|
||||||
// await preferences.set(extensionName, key, value)
|
|
||||||
// await execute(ExtensionService.OnPreferencesUpdate, {})
|
|
||||||
}
|
|
||||||
toaster({
|
|
||||||
title: formatExtensionsName(extensionName),
|
|
||||||
description: 'Successfully updated preferences',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-auto w-full lg:mt-10 lg:w-1/2">
|
|
||||||
<h6 className="mb-6 text-lg font-semibold capitalize">
|
|
||||||
{formatExtensionsName(extensionName)}
|
|
||||||
</h6>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
||||||
{preferenceItems
|
|
||||||
.filter((x: any) => x.extensionName === extensionName)
|
|
||||||
?.map((e: any) => (
|
|
||||||
<FormField
|
|
||||||
key={e.preferenceKey}
|
|
||||||
control={form.control}
|
|
||||||
name={e.preferenceKey}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{e.preferenceName}</FormLabel>
|
|
||||||
<FormDescription className="mb-2">
|
|
||||||
{e.preferenceDescription}
|
|
||||||
</FormDescription>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={`Enter your ${e.preferenceName}`}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<div className="pt-4">
|
|
||||||
<Button type="submit" block>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PreferenceExtensions
|
|
||||||
@ -1,38 +1,17 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useContext } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { Button } from '@janhq/uikit'
|
import { Button } from '@janhq/uikit'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { formatExtensionsName } from '@/utils/converter'
|
import { formatExtensionsName } from '@/utils/converter'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
|
import Extension from '@/extension/Extension'
|
||||||
|
|
||||||
const ExtensionCatalog = () => {
|
const ExtensionCatalog = () => {
|
||||||
const [activeExtensions, setActiveExtensions] = useState<any[]>([])
|
const [activeExtensions, setActiveExtensions] = useState<Extension[]>([])
|
||||||
const [extensionCatalog, setExtensionCatalog] = useState<any[]>([])
|
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
||||||
const { experimentalFeature } = useContext(FeatureToggleContext)
|
|
||||||
/**
|
|
||||||
* Loads the extension catalog module from a CDN and sets it as the extension catalog state.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
if (!window.electronAPI) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get extension manifest
|
|
||||||
import(/* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}`).then(
|
|
||||||
(data) => {
|
|
||||||
if (Array.isArray(data.default) && experimentalFeature)
|
|
||||||
setExtensionCatalog(data.default)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}, [experimentalFeature])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the active extensions and their preferences from the `extensions` and `preferences` modules.
|
* Fetches the active extensions and their preferences from the `extensions` and `preferences` modules.
|
||||||
* If the `experimentComponent` extension point is available, it executes the extension point and
|
* If the `experimentComponent` extension point is available, it executes the extension point and
|
||||||
@ -90,26 +69,7 @@ const ExtensionCatalog = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block w-full">
|
<div className="block w-full">
|
||||||
{extensionCatalog
|
{activeExtensions.map((item, i) => {
|
||||||
.concat(
|
|
||||||
activeExtensions.filter(
|
|
||||||
(e) => !(extensionCatalog ?? []).some((p) => p.name === e.name)
|
|
||||||
) ?? []
|
|
||||||
)
|
|
||||||
.map((item, i) => {
|
|
||||||
const isActiveExtension = activeExtensions.some(
|
|
||||||
(x) => x.name === item.name
|
|
||||||
)
|
|
||||||
const installedExtension = activeExtensions.filter(
|
|
||||||
(p) => p.name === item.name
|
|
||||||
)[0]
|
|
||||||
const updateVersionExtensions = Number(
|
|
||||||
installedExtension?.version.replaceAll('.', '')
|
|
||||||
)
|
|
||||||
|
|
||||||
const hasUpdateVersionExtensions =
|
|
||||||
item.version.replaceAll('.', '') > updateVersionExtensions
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@ -118,7 +78,7 @@ const ExtensionCatalog = () => {
|
|||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
{formatExtensionsName(item.name)}
|
{formatExtensionsName(item.name ?? item.description ?? '')}
|
||||||
</h6>
|
</h6>
|
||||||
<p className="whitespace-pre-wrap font-semibold leading-relaxed ">
|
<p className="whitespace-pre-wrap font-semibold leading-relaxed ">
|
||||||
v{item.version}
|
v{item.version}
|
||||||
@ -127,16 +87,6 @@ const ExtensionCatalog = () => {
|
|||||||
<p className="whitespace-pre-wrap leading-relaxed ">
|
<p className="whitespace-pre-wrap leading-relaxed ">
|
||||||
{item.description}
|
{item.description}
|
||||||
</p>
|
</p>
|
||||||
{isActiveExtension && (
|
|
||||||
<div className="flex items-center gap-x-2">
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed ">
|
|
||||||
Installed{' '}
|
|
||||||
{hasUpdateVersionExtensions
|
|
||||||
? `v${installedExtension.version}`
|
|
||||||
: 'the latest version'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -9,8 +9,7 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import Advanced from '@/screens/Settings/Advanced'
|
import Advanced from '@/screens/Settings/Advanced'
|
||||||
import AppearanceOptions from '@/screens/Settings/Appearance'
|
import AppearanceOptions from '@/screens/Settings/Appearance'
|
||||||
import ExtensionCatalog from '@/screens/Settings/CoreExtensions/ExtensionsCatalog'
|
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
||||||
import PreferenceExtensions from '@/screens/Settings/CoreExtensions/PreferenceExtensions'
|
|
||||||
|
|
||||||
import Models from '@/screens/Settings/Models'
|
import Models from '@/screens/Settings/Models'
|
||||||
|
|
||||||
@ -19,8 +18,6 @@ import { formatExtensionsName } from '@/utils/converter'
|
|||||||
const SettingsScreen = () => {
|
const SettingsScreen = () => {
|
||||||
const [activeStaticMenu, setActiveStaticMenu] = useState('My Models')
|
const [activeStaticMenu, setActiveStaticMenu] = useState('My Models')
|
||||||
const [menus, setMenus] = useState<any[]>([])
|
const [menus, setMenus] = useState<any[]>([])
|
||||||
const [preferenceItems, setPreferenceItems] = useState<any[]>([])
|
|
||||||
const [preferenceValues, setPreferenceValues] = useState<any[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const menu = ['My Models', 'My Settings', 'Advanced Settings']
|
const menu = ['My Models', 'My Settings', 'Advanced Settings']
|
||||||
@ -31,12 +28,6 @@ const SettingsScreen = () => {
|
|||||||
setMenus(menu)
|
setMenus(menu)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const preferenceExtensions = preferenceItems
|
|
||||||
.map((x) => x.extensionnName)
|
|
||||||
.filter((x, i) => {
|
|
||||||
// return prefere/nceItems.map((x) => x.extensionName).indexOf(x) === i
|
|
||||||
})
|
|
||||||
|
|
||||||
const [activePreferenceExtension, setActivePreferenceExtension] = useState('')
|
const [activePreferenceExtension, setActivePreferenceExtension] = useState('')
|
||||||
|
|
||||||
const handleShowOptions = (menu: string) => {
|
const handleShowOptions = (menu: string) => {
|
||||||
@ -52,15 +43,6 @@ const SettingsScreen = () => {
|
|||||||
|
|
||||||
case 'My Models':
|
case 'My Models':
|
||||||
return <Models />
|
return <Models />
|
||||||
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<PreferenceExtensions
|
|
||||||
extensionName={menu}
|
|
||||||
preferenceItems={preferenceItems}
|
|
||||||
preferenceValues={preferenceValues}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,45 +79,6 @@ const SettingsScreen = () => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 flex-shrink-0">
|
|
||||||
{preferenceExtensions.length > 0 && (
|
|
||||||
<label className="font-bold uppercase text-muted-foreground">
|
|
||||||
Core Extensions
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<div className="mt-2 font-medium">
|
|
||||||
{preferenceExtensions.map((menu, i) => {
|
|
||||||
const isActive = activePreferenceExtension === menu
|
|
||||||
return (
|
|
||||||
<div key={i} className="relative my-0.5 block py-1.5">
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
setActivePreferenceExtension(menu)
|
|
||||||
setActiveStaticMenu('')
|
|
||||||
}}
|
|
||||||
className="block w-full cursor-pointer"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={twMerge(
|
|
||||||
'capitalize',
|
|
||||||
isActive && 'relative z-10'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{formatExtensionsName(String(menu))}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{isActive ? (
|
|
||||||
<m.div
|
|
||||||
className="absolute inset-0 -left-3 h-full w-[calc(100%+24px)] rounded-md bg-primary/50"
|
|
||||||
layoutId="active-static-menu"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user