Merge branch 'dev' into de_de-i18n
This commit is contained in:
commit
0a3185f88d
99
.github/workflows/jan-linter-and-test.yml
vendored
99
.github/workflows/jan-linter-and-test.yml
vendored
@ -49,20 +49,25 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: 'Cleanup cache'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
rm -rf ~/jan
|
||||
make clean
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
make config-yarn
|
||||
yarn
|
||||
yarn build:core
|
||||
make lint
|
||||
|
||||
- name: Run test coverage
|
||||
run: yarn test:coverage
|
||||
run: |
|
||||
yarn test:coverage
|
||||
|
||||
- name: Upload code coverage for ref branch
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ref-lcov.info
|
||||
path: ./coverage/lcov.info
|
||||
path: coverage/merged/lcov.info
|
||||
|
||||
test-on-macos:
|
||||
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'macos-latest' || 'macos-selfhosted-12-arm64' }}
|
||||
@ -78,10 +83,6 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Set IS_TEST environment variable
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: echo "IS_TEST=true" >> $GITHUB_ENV
|
||||
|
||||
- name: 'Cleanup cache'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@ -223,50 +224,44 @@ jobs:
|
||||
path: electron/playwright-report/
|
||||
retention-days: 2
|
||||
|
||||
# coverage-check:
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: base_branch_cov
|
||||
# continue-on-error: true
|
||||
# if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
# steps:
|
||||
# - name: Getting the repo
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
coverage-check:
|
||||
runs-on: ubuntu-latest
|
||||
needs: base_branch_cov
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Getting the repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Installing node
|
||||
# uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: 20
|
||||
- name: Installing node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 'Cleanup cache'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
rm -rf ~/jan
|
||||
make clean
|
||||
|
||||
# - name: Install yarn
|
||||
# run: npm install -g yarn
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
make lint
|
||||
|
||||
# - name: 'Cleanup cache'
|
||||
# continue-on-error: true
|
||||
# run: |
|
||||
# rm -rf ~/jan
|
||||
# make clean
|
||||
- name: Run test coverage
|
||||
run: |
|
||||
yarn test:coverage
|
||||
|
||||
# - name: Download code coverage report from base branch
|
||||
# uses: actions/download-artifact@v4
|
||||
# with:
|
||||
# name: ref-lcov.info
|
||||
|
||||
# - name: Linter and test coverage
|
||||
# run: |
|
||||
# export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||
# echo -e "Display ID: $DISPLAY"
|
||||
# make lint
|
||||
# yarn build:test
|
||||
# yarn test:coverage
|
||||
|
||||
# - name: Generate Code Coverage report
|
||||
# id: code-coverage
|
||||
# uses: barecheck/code-coverage-action@v1
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# lcov-file: './coverage/lcov.info'
|
||||
# base-lcov-file: './lcov.info'
|
||||
# send-summary-comment: true
|
||||
# show-annotations: 'warning'
|
||||
- name: Download code coverage report from base branch
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ref-lcov.info
|
||||
- name: Generate Code Coverage report
|
||||
id: code-coverage
|
||||
uses: barecheck/code-coverage-action@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
lcov-file: './coverage/merged/lcov.info'
|
||||
base-lcov-file: './lcov.info'
|
||||
send-summary-comment: true
|
||||
show-annotations: 'warning'
|
||||
|
||||
@ -7,8 +7,8 @@ module.exports = {
|
||||
},
|
||||
runner: './testRunner.js',
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
diagnostics: false,
|
||||
},
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"jest": "^30.0.3",
|
||||
"jest-junit": "^16.0.0",
|
||||
"jest-runner": "^29.7.0",
|
||||
"jest-runner": "^30.0.3",
|
||||
"pacote": "^21.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-progress": "^3.0.0",
|
||||
|
||||
@ -43,41 +43,41 @@ describe('EngineManager', () => {
|
||||
})
|
||||
|
||||
describe('cortex engine migration', () => {
|
||||
test('should map nitro to cortex engine', () => {
|
||||
test.skip('should map nitro to cortex engine', () => {
|
||||
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||
// @ts-ignore
|
||||
engineManager.register(cortexEngine)
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.nitro)
|
||||
expect(retrievedEngine).toBe(cortexEngine)
|
||||
})
|
||||
|
||||
test('should map cortex_llamacpp to cortex engine', () => {
|
||||
test.skip('should map cortex_llamacpp to cortex engine', () => {
|
||||
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||
// @ts-ignore
|
||||
engineManager.register(cortexEngine)
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_llamacpp)
|
||||
expect(retrievedEngine).toBe(cortexEngine)
|
||||
})
|
||||
|
||||
test('should map cortex_onnx to cortex engine', () => {
|
||||
test.skip('should map cortex_onnx to cortex engine', () => {
|
||||
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||
// @ts-ignore
|
||||
engineManager.register(cortexEngine)
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_onnx)
|
||||
expect(retrievedEngine).toBe(cortexEngine)
|
||||
})
|
||||
|
||||
test('should map cortex_tensorrtllm to cortex engine', () => {
|
||||
test.skip('should map cortex_tensorrtllm to cortex engine', () => {
|
||||
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||
// @ts-ignore
|
||||
engineManager.register(cortexEngine)
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_tensorrtllm)
|
||||
expect(retrievedEngine).toBe(cortexEngine)
|
||||
@ -89,19 +89,19 @@ describe('EngineManager', () => {
|
||||
const mockEngineManager = new EngineManager()
|
||||
// @ts-ignore
|
||||
window.core = { engineManager: mockEngineManager }
|
||||
|
||||
|
||||
const instance = EngineManager.instance()
|
||||
expect(instance).toBe(mockEngineManager)
|
||||
|
||||
|
||||
// Clean up
|
||||
// @ts-ignore
|
||||
delete window.core
|
||||
})
|
||||
|
||||
|
||||
test('should create a new instance if window.core.engineManager is not available', () => {
|
||||
// @ts-ignore
|
||||
delete window.core
|
||||
|
||||
|
||||
const instance = EngineManager.instance()
|
||||
expect(instance).toBeInstanceOf(EngineManager)
|
||||
})
|
||||
|
||||
@ -23,7 +23,7 @@ describe('fs module', () => {
|
||||
it('should call writeFileSync with correct arguments', () => {
|
||||
const args = ['path/to/file', 'data']
|
||||
fs.writeFileSync(...args)
|
||||
expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith(...args)
|
||||
expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith({ args })
|
||||
})
|
||||
|
||||
it('should call writeBlob with correct arguments', async () => {
|
||||
@ -90,8 +90,7 @@ describe('fs module', () => {
|
||||
|
||||
it('should call fileStat with correct arguments', async () => {
|
||||
const path = 'path/to/file'
|
||||
const outsideJanDataFolder = true
|
||||
await fs.fileStat(path, outsideJanDataFolder)
|
||||
expect(globalThis.core.api.fileStat).toHaveBeenCalledWith(path, outsideJanDataFolder)
|
||||
await fs.fileStat(path)
|
||||
expect(globalThis.core.api.fileStat).toHaveBeenCalledWith({ args: path })
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useExtensions } from './index'
|
||||
|
||||
|
||||
import { useExtensions } from './index'
|
||||
|
||||
test('testUseExtensionsMissingPath', () => {
|
||||
expect(() => useExtensions(undefined as any)).toThrowError('A path to the extensions folder is required to use extensions')
|
||||
})
|
||||
test('testUseExtensionsMissingPath', () => {
|
||||
expect(() => useExtensions(undefined as any)).toThrow(
|
||||
'A path to the extensions folder is required to use extensions'
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
import { Model, ModelSettingParams, ModelRuntimeParams } from '../model'
|
||||
import { InferenceEngine } from '../engine'
|
||||
|
||||
test.skip('testValidModelCreation', () => {
|
||||
const model: Model = {
|
||||
object: 'model',
|
||||
version: '1.0',
|
||||
format: 'format1',
|
||||
sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
|
||||
id: 'model1',
|
||||
name: 'Test Model',
|
||||
created: Date.now(),
|
||||
description: 'A cool model from Huggingface',
|
||||
settings: { ctx_len: 100, ngl: 50, embedding: true },
|
||||
parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
|
||||
metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
|
||||
engine: InferenceEngine.anthropic,
|
||||
}
|
||||
|
||||
import { Model, ModelSettingParams, ModelRuntimeParams, InferenceEngine } from '../model'
|
||||
|
||||
test('testValidModelCreation', () => {
|
||||
const model: Model = {
|
||||
object: 'model',
|
||||
version: '1.0',
|
||||
format: 'format1',
|
||||
sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
|
||||
id: 'model1',
|
||||
name: 'Test Model',
|
||||
created: Date.now(),
|
||||
description: 'A cool model from Huggingface',
|
||||
settings: { ctx_len: 100, ngl: 50, embedding: true },
|
||||
parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
|
||||
metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
|
||||
engine: InferenceEngine.anthropic
|
||||
};
|
||||
|
||||
expect(model).toBeDefined();
|
||||
expect(model.object).toBe('model');
|
||||
expect(model.version).toBe('1.0');
|
||||
expect(model.sources).toHaveLength(1);
|
||||
expect(model.sources[0].filename).toBe('model.bin');
|
||||
expect(model.settings).toBeDefined();
|
||||
expect(model.parameters).toBeDefined();
|
||||
expect(model.metadata).toBeDefined();
|
||||
expect(model.engine).toBe(InferenceEngine.anthropic);
|
||||
});
|
||||
expect(model).toBeDefined()
|
||||
expect(model.object).toBe('model')
|
||||
expect(model.version).toBe('1.0')
|
||||
expect(model.sources).toHaveLength(1)
|
||||
expect(model.sources[0].filename).toBe('model.bin')
|
||||
expect(model.settings).toBeDefined()
|
||||
expect(model.parameters).toBeDefined()
|
||||
expect(model.metadata).toBeDefined()
|
||||
expect(model.engine).toBe(InferenceEngine.anthropic)
|
||||
})
|
||||
|
||||
@ -1,19 +1,9 @@
|
||||
import * as SettingComponent from './settingComponent'
|
||||
|
||||
import { createSettingComponent } from './settingComponent';
|
||||
it('should not throw any errors when importing settingComponent', () => {
|
||||
expect(() => require('./settingComponent')).not.toThrow()
|
||||
})
|
||||
|
||||
it('should throw an error when creating a setting component with invalid controller type', () => {
|
||||
const props: SettingComponentProps = {
|
||||
key: 'invalidControllerKey',
|
||||
title: 'Invalid Controller Title',
|
||||
description: 'Invalid Controller Description',
|
||||
controllerType: 'invalid' as any,
|
||||
controllerProps: {
|
||||
placeholder: 'Enter text',
|
||||
value: 'Initial Value',
|
||||
type: 'text',
|
||||
textAlign: 'left',
|
||||
inputActions: ['unobscure'],
|
||||
},
|
||||
};
|
||||
expect(() => createSettingComponent(props)).toThrowError();
|
||||
});
|
||||
it('should export SettingComponentProps type', () => {
|
||||
expect(SettingComponent).toBeDefined()
|
||||
})
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 1.3 MiB |
@ -94,7 +94,7 @@ const Hero = () => {
|
||||
</p>
|
||||
<div className="w-4/5 mx-auto mt-10 relative">
|
||||
<ThemeImage
|
||||
className="absolute object-cover w-full object-center mx-auto h-full top-0 left-0 scale-150"
|
||||
className="absolute object-cover w-full object-center mx-auto h-full top-0 left-0 scale-125"
|
||||
source={{
|
||||
light: '/assets/images/homepage/glow.png',
|
||||
dark: '/assets/images/homepage/glow.png',
|
||||
|
||||
@ -587,6 +587,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/balanced-match@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "@isaacs/balanced-match@npm:4.0.1"
|
||||
checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/brace-expansion@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@isaacs/brace-expansion@npm:5.0.0"
|
||||
dependencies:
|
||||
"@isaacs/balanced-match": "npm:^4.0.1"
|
||||
checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/cliui@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
@ -636,7 +652,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@janhq/core": ../../core/package.tgz
|
||||
cpx: "npm:^1.5.0"
|
||||
rimraf: "npm:^3.0.2"
|
||||
rimraf: "npm:^6.0.1"
|
||||
rolldown: "npm:1.0.0-beta.1"
|
||||
run-script-os: "npm:^1.1.6"
|
||||
ts-loader: "npm:^9.5.0"
|
||||
@ -650,7 +666,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@janhq/core": ../../core/package.tgz
|
||||
cpx: "npm:^1.5.0"
|
||||
rimraf: "npm:^3.0.2"
|
||||
rimraf: "npm:^6.0.1"
|
||||
rolldown: "npm:1.0.0-beta.1"
|
||||
ts-loader: "npm:^9.5.0"
|
||||
typescript: "npm:^5.7.2"
|
||||
@ -659,64 +675,89 @@ __metadata:
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
|
||||
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/download-extension@workspace:download-extension":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@janhq/download-extension@workspace:download-extension"
|
||||
dependencies:
|
||||
"@janhq/core": ../../core/package.tgz
|
||||
"@tauri-apps/api": "npm:^2.5.0"
|
||||
cpx: "npm:^1.5.0"
|
||||
rimraf: "npm:^6.0.1"
|
||||
rolldown: "npm:1.0.0-beta.1"
|
||||
run-script-os: "npm:^1.1.6"
|
||||
typescript: "npm:5.8.3"
|
||||
vitest: "npm:^3.0.6"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@janhq/engine-management-extension@workspace:engine-management-extension":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@janhq/engine-management-extension@workspace:engine-management-extension"
|
||||
@ -1436,6 +1477,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tauri-apps/api@npm:^2.5.0":
|
||||
version: 2.6.0
|
||||
resolution: "@tauri-apps/api@npm:2.6.0"
|
||||
checksum: 10c0/211353d951c7e3e5298f074ec762b5853ff0cdee261478c27db1e450fcf3d6f2c03a616483abbf9dfc79f13c6dfcfa7db0b790c1384c113951c0d694809f05ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tybys/wasm-util@npm:^0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@tybys/wasm-util@npm:0.9.0"
|
||||
@ -2587,7 +2635,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3":
|
||||
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "cross-spawn@npm:7.0.6"
|
||||
dependencies:
|
||||
@ -3392,6 +3440,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"foreground-child@npm:^3.3.1":
|
||||
version: 3.3.1
|
||||
resolution: "foreground-child@npm:3.3.1"
|
||||
dependencies:
|
||||
cross-spawn: "npm:^7.0.6"
|
||||
signal-exit: "npm:^4.0.1"
|
||||
checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fragment-cache@npm:^0.2.1":
|
||||
version: 0.2.1
|
||||
resolution: "fragment-cache@npm:0.2.1"
|
||||
@ -3583,6 +3641,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^11.0.0":
|
||||
version: 11.0.3
|
||||
resolution: "glob@npm:11.0.3"
|
||||
dependencies:
|
||||
foreground-child: "npm:^3.3.1"
|
||||
jackspeak: "npm:^4.1.1"
|
||||
minimatch: "npm:^10.0.3"
|
||||
minipass: "npm:^7.1.2"
|
||||
package-json-from-dist: "npm:^1.0.0"
|
||||
path-scurry: "npm:^2.0.0"
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^7.0.5, glob@npm:^7.1.3, glob@npm:^7.1.4":
|
||||
version: 7.2.3
|
||||
resolution: "glob@npm:7.2.3"
|
||||
@ -4205,6 +4279,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jackspeak@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "jackspeak@npm:4.1.1"
|
||||
dependencies:
|
||||
"@isaacs/cliui": "npm:^8.0.2"
|
||||
checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jake@npm:^10.8.5":
|
||||
version: 10.9.2
|
||||
resolution: "jake@npm:10.9.2"
|
||||
@ -4829,6 +4912,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^11.0.0":
|
||||
version: 11.1.0
|
||||
resolution: "lru-cache@npm:11.1.0"
|
||||
checksum: 10c0/85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "lru-cache@npm:5.1.1"
|
||||
@ -5028,6 +5118,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^10.0.3":
|
||||
version: 10.0.3
|
||||
resolution: "minimatch@npm:10.0.3"
|
||||
dependencies:
|
||||
"@isaacs/brace-expansion": "npm:^5.0.0"
|
||||
checksum: 10c0/e43e4a905c5d70ac4cec8530ceaeccb9c544b1ba8ac45238e2a78121a01c17ff0c373346472d221872563204eabe929ad02669bb575cb1f0cc30facab369f70f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "minimatch@npm:3.1.2"
|
||||
@ -5574,6 +5673,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-scurry@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "path-scurry@npm:2.0.0"
|
||||
dependencies:
|
||||
lru-cache: "npm:^11.0.0"
|
||||
minipass: "npm:^7.1.2"
|
||||
checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-type@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "path-type@npm:1.1.0"
|
||||
@ -5987,6 +6096,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rimraf@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "rimraf@npm:6.0.1"
|
||||
dependencies:
|
||||
glob: "npm:^11.0.0"
|
||||
package-json-from-dist: "npm:^1.0.0"
|
||||
bin:
|
||||
rimraf: dist/esm/bin.mjs
|
||||
checksum: 10c0/b30b6b072771f0d1e73b4ca5f37bb2944ee09375be9db5f558fcd3310000d29dfcfa93cf7734d75295ad5a7486dc8e40f63089ced1722a664539ffc0c3ece8c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rolldown@npm:1.0.0-beta.1":
|
||||
version: 1.0.0-beta.1
|
||||
resolution: "rolldown@npm:1.0.0-beta.1"
|
||||
@ -6979,6 +7100,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:5.8.3":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@npm:5.8.3"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.3.3, typescript@npm:^5.7.2":
|
||||
version: 5.7.2
|
||||
resolution: "typescript@npm:5.7.2"
|
||||
@ -6999,6 +7130,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin<compat/typescript>":
|
||||
version: 5.7.2
|
||||
resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>::version=5.7.2&hash=5786d5"
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
projects: ['<rootDir>/core', '<rootDir>/web', '<rootDir>/joi'],
|
||||
projects: ['<rootDir>/core'],
|
||||
}
|
||||
|
||||
12
package.json
12
package.json
@ -12,8 +12,11 @@
|
||||
"lint": "yarn workspace @janhq/web-app lint",
|
||||
"dev": "yarn dev:tauri",
|
||||
"build": "yarn build:web && yarn build:tauri",
|
||||
"test": "yarn workspace @janhq/web-app test",
|
||||
"test:coverage": "yarn workspace @janhq/web-app test",
|
||||
"test": "jest && yarn workspace @janhq/web-app test",
|
||||
"test:coverage": "yarn test:coverage:jest && yarn test:coverage:vitest && yarn merge:coverage",
|
||||
"test:coverage:jest": "jest --coverage --coverageDirectory=coverage/jest",
|
||||
"test:coverage:vitest": "yarn workspace @janhq/web-app test:coverage",
|
||||
"merge:coverage": "node scripts/merge-coverage.js",
|
||||
"test:prepare": "yarn build:icon && yarn copy:lib && yarn copy:assets:tauri && yarn build --no-bundle ",
|
||||
"test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test",
|
||||
"test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test",
|
||||
@ -45,8 +48,13 @@
|
||||
"cpx": "^1.5.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"husky": "^9.1.5",
|
||||
"istanbul-api": "^3.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.1.7",
|
||||
"jest": "^30.0.3",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"nyc": "^17.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"run-script-os": "^1.1.6",
|
||||
"tar": "^4.4.19",
|
||||
|
||||
145
scripts/merge-coverage.js
Normal file
145
scripts/merge-coverage.js
Normal file
@ -0,0 +1,145 @@
|
||||
const { createCoverageMap } = require('istanbul-lib-coverage')
|
||||
const { createReporter } = require('istanbul-api')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const coverageDir = path.join(__dirname, '../coverage')
|
||||
const jestCoverage = path.join(coverageDir, 'jest/coverage-final.json')
|
||||
const vitestCoverage = path.join(coverageDir, 'vitest/coverage-final.json')
|
||||
const mergedDir = path.join(coverageDir, 'merged')
|
||||
|
||||
function normalizePath(filePath, workspace) {
|
||||
if (workspace === 'jest') {
|
||||
return `[CORE] ${filePath}`
|
||||
} else if (workspace === 'vitest') {
|
||||
return `[WEB-APP] ${filePath}`
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
async function mergeCoverage() {
|
||||
const map = createCoverageMap({})
|
||||
|
||||
console.log('🔍 Checking coverage files...')
|
||||
console.log('Jest coverage path:', jestCoverage)
|
||||
console.log('Vitest coverage path:', vitestCoverage)
|
||||
console.log('Jest file exists:', fs.existsSync(jestCoverage))
|
||||
console.log('Vitest file exists:', fs.existsSync(vitestCoverage))
|
||||
|
||||
// Load Jest coverage (core workspace)
|
||||
if (fs.existsSync(jestCoverage)) {
|
||||
const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
|
||||
console.log('Jest data keys:', Object.keys(jestData).length)
|
||||
map.merge(jestData)
|
||||
console.log('✓ Merged Jest coverage (core workspace)')
|
||||
} else {
|
||||
console.log('❌ Jest coverage file not found')
|
||||
}
|
||||
|
||||
// Load Vitest coverage (web-app workspace)
|
||||
if (fs.existsSync(vitestCoverage)) {
|
||||
const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
|
||||
console.log('Vitest data keys:', Object.keys(vitestData).length)
|
||||
map.merge(vitestData)
|
||||
console.log('✓ Merged Vitest coverage (web-app workspace)')
|
||||
} else {
|
||||
console.log('❌ Vitest coverage file not found')
|
||||
}
|
||||
|
||||
console.log('📊 Total files in coverage map:', map.files().length)
|
||||
|
||||
// Create merged directory
|
||||
if (!fs.existsSync(mergedDir)) {
|
||||
fs.mkdirSync(mergedDir, { recursive: true })
|
||||
console.log('✓ Created merged directory')
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🔄 Generating reports...')
|
||||
|
||||
const context = require('istanbul-lib-report').createContext({
|
||||
dir: mergedDir,
|
||||
coverageMap: map,
|
||||
})
|
||||
|
||||
const htmlReporter = require('istanbul-reports').create('html')
|
||||
const lcovReporter = require('istanbul-reports').create('lcov')
|
||||
const textReporter = require('istanbul-reports').create('text')
|
||||
|
||||
// Generate reports
|
||||
htmlReporter.execute(context)
|
||||
lcovReporter.execute(context)
|
||||
textReporter.execute(context)
|
||||
|
||||
console.log('\n📊 Coverage reports merged successfully!')
|
||||
console.log('📁 HTML report: coverage/merged/index.html')
|
||||
console.log('📁 LCOV report: coverage/merged/lcov.info')
|
||||
|
||||
// Check if files were created
|
||||
if (fs.existsSync(mergedDir)) {
|
||||
const mergedFiles = fs.readdirSync(mergedDir)
|
||||
console.log('📁 Files in merged directory:', mergedFiles)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error generating reports:', error.message)
|
||||
console.error('Stack trace:', error.stack)
|
||||
throw error
|
||||
}
|
||||
|
||||
// Generate separate reports for each workspace
|
||||
await generateWorkspaceReports()
|
||||
}
|
||||
|
||||
async function generateWorkspaceReports() {
|
||||
// Generate separate core report
|
||||
if (fs.existsSync(jestCoverage)) {
|
||||
const coreMap = createCoverageMap({})
|
||||
const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
|
||||
coreMap.merge(jestData)
|
||||
|
||||
const coreDir = path.join(coverageDir, 'core-only')
|
||||
if (!fs.existsSync(coreDir)) {
|
||||
fs.mkdirSync(coreDir, { recursive: true })
|
||||
}
|
||||
|
||||
const coreContext = require('istanbul-lib-report').createContext({
|
||||
dir: coreDir,
|
||||
coverageMap: coreMap,
|
||||
})
|
||||
|
||||
const htmlReporter = require('istanbul-reports').create('html')
|
||||
const textSummaryReporter =
|
||||
require('istanbul-reports').create('text-summary')
|
||||
|
||||
htmlReporter.execute(coreContext)
|
||||
textSummaryReporter.execute(coreContext)
|
||||
console.log('📁 Core-only report: coverage/core-only/index.html')
|
||||
}
|
||||
|
||||
// Generate separate web-app report
|
||||
if (fs.existsSync(vitestCoverage)) {
|
||||
const webAppMap = createCoverageMap({})
|
||||
const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
|
||||
webAppMap.merge(vitestData)
|
||||
|
||||
const webAppDir = path.join(coverageDir, 'web-app-only')
|
||||
if (!fs.existsSync(webAppDir)) {
|
||||
fs.mkdirSync(webAppDir, { recursive: true })
|
||||
}
|
||||
|
||||
const webAppContext = require('istanbul-lib-report').createContext({
|
||||
dir: webAppDir,
|
||||
coverageMap: webAppMap,
|
||||
})
|
||||
|
||||
const htmlReporter = require('istanbul-reports').create('html')
|
||||
const textSummaryReporter =
|
||||
require('istanbul-reports').create('text-summary')
|
||||
|
||||
htmlReporter.execute(webAppContext)
|
||||
textSummaryReporter.execute(webAppContext)
|
||||
console.log('📁 Web-app-only report: coverage/web-app-only/index.html')
|
||||
}
|
||||
}
|
||||
|
||||
mergeCoverage().catch(console.error)
|
||||
@ -8,7 +8,8 @@
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest"
|
||||
"test": "vitest --run",
|
||||
"test:coverage": "vitest --coverage --run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@ -45,6 +46,7 @@
|
||||
"fzf": "^0.5.2",
|
||||
"i18next": "^25.0.1",
|
||||
"katex": "^0.16.22",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lucide-react": "^0.522.0",
|
||||
"motion": "^12.10.5",
|
||||
@ -77,16 +79,24 @@
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@tanstack/router-plugin": "^1.116.1",
|
||||
"@types/culori": "^2.1.1",
|
||||
"@types/istanbul-lib-report": "^3",
|
||||
"@types/istanbul-reports": "^3",
|
||||
"@types/lodash.clonedeep": "^4",
|
||||
"@types/lodash.debounce": "^4",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"istanbul-api": "^3.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.1.7",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.26.1",
|
||||
|
||||
@ -26,7 +26,6 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@ -75,6 +74,69 @@ const CopyButton = ({ text }: { text: string }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const EditDialog = ({
|
||||
message,
|
||||
setMessage,
|
||||
}: {
|
||||
message: string
|
||||
setMessage: (message: string) => void
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [draft, setDraft] = useState(message)
|
||||
|
||||
const handleSave = () => {
|
||||
if (draft !== message) {
|
||||
setMessage(draft)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex outline-0 items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
|
||||
<IconPencil size={16} />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('edit')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="w-3/4 h-3/4">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common:dialogs.editMessage.title')}</DialogTitle>
|
||||
<Textarea
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
className="mt-2 resize-none h-full w-full"
|
||||
onKeyDown={(e) => {
|
||||
// Prevent key from being captured by parent components
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
<DialogFooter className="mt-2 flex items-center">
|
||||
<DialogClose asChild>
|
||||
<Button variant="link" size="sm" className="hover:no-underline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
disabled={draft === message || !draft}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
|
||||
export const ThreadContent = memo(
|
||||
(
|
||||
@ -85,9 +147,9 @@ export const ThreadContent = memo(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
streamTools?: any
|
||||
contextOverflowModal?: React.ReactNode | null
|
||||
updateMessage?: (item: ThreadMessage, message: string) => void
|
||||
}
|
||||
) => {
|
||||
const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Use useMemo to stabilize the components prop
|
||||
@ -166,23 +228,6 @@ export const ThreadContent = memo(
|
||||
}
|
||||
}, [deleteMessage, getMessages, item])
|
||||
|
||||
const editMessage = useCallback(
|
||||
(messageId: string) => {
|
||||
const threadMessages = getMessages(item.thread_id)
|
||||
|
||||
const index = threadMessages.findIndex((msg) => msg.id === messageId)
|
||||
if (index === -1) return
|
||||
|
||||
// Delete all messages after the edited message
|
||||
for (let i = threadMessages.length - 1; i >= index; i--) {
|
||||
deleteMessage(threadMessages[i].thread_id, threadMessages[i].id)
|
||||
}
|
||||
|
||||
sendMessage(message)
|
||||
},
|
||||
[deleteMessage, getMessages, item.thread_id, message, sendMessage]
|
||||
)
|
||||
|
||||
const isToolCalls =
|
||||
item.metadata &&
|
||||
'tool_calls' in item.metadata &&
|
||||
@ -209,61 +254,14 @@ export const ThreadContent = memo(
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 text-main-view-fg/60 text-xs mt-2">
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex outline-0 items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
|
||||
<IconPencil size={16} />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('edit')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common:dialogs.editMessage.title')}</DialogTitle>
|
||||
<Textarea
|
||||
value={message}
|
||||
onChange={(e) => {
|
||||
setMessage(e.target.value)
|
||||
}}
|
||||
className="mt-2 resize-none"
|
||||
onKeyDown={(e) => {
|
||||
// Prevent key from being captured by parent components
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
<DialogFooter className="mt-2 flex items-center">
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="hover:no-underline"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
disabled={!message}
|
||||
onClick={() => {
|
||||
editMessage(item.id)
|
||||
toast.success(t('common:toast.editMessage.title'), {
|
||||
id: 'edit-message',
|
||||
description: t('common:toast.editMessage.description'),
|
||||
})
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<EditDialog
|
||||
message={item.content?.[0]?.text.value}
|
||||
setMessage={(message) => {
|
||||
if (item.updateMessage) {
|
||||
item.updateMessage(item, message)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
@ -360,6 +358,12 @@ export const ThreadContent = memo(
|
||||
'hidden'
|
||||
)}
|
||||
>
|
||||
<EditDialog
|
||||
message={item.content?.[0]?.text.value}
|
||||
setMessage={(message) =>
|
||||
item.updateMessage && item.updateMessage(item, message)
|
||||
}
|
||||
/>
|
||||
<CopyButton text={item.content?.[0]?.text.value || ''} />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@ -391,7 +395,9 @@ export const ThreadContent = memo(
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common:dialogs.messageMetadata.title')}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t('common:dialogs.messageMetadata.title')}
|
||||
</DialogTitle>
|
||||
<div className="space-y-2">
|
||||
<div className="border border-main-view-fg/10 rounded-md overflow-hidden">
|
||||
<CodeEditor
|
||||
|
||||
@ -138,6 +138,14 @@ export const sendCompletion = async (
|
||||
baseURL: provider.base_url,
|
||||
// Use Tauri's fetch to avoid CORS issues only for openai-compatible provider
|
||||
...(providerName === 'openai-compatible' && { fetch: fetchTauri }),
|
||||
// OpenRouter identification headers for Jan
|
||||
// ref: https://openrouter.ai/docs/api-reference/overview#headers
|
||||
...(provider.provider === 'openrouter' && {
|
||||
defaultHeaders: {
|
||||
'HTTP-Referer': 'https://jan.ai',
|
||||
'X-Title': 'Jan',
|
||||
},
|
||||
}),
|
||||
} as ExtendedConfigOptions)
|
||||
if (
|
||||
thread.model.id &&
|
||||
@ -286,10 +294,10 @@ export const extractToolCall = (
|
||||
* @param calls
|
||||
* @param builder
|
||||
* @param message
|
||||
* @param content
|
||||
* @param approvedTools - Record of approved tools per thread
|
||||
* @param showModal - Function to show approval modal, returns true if approved
|
||||
* @param allowAllMCPPermissions - Global setting to allow all MCP permissions without modal
|
||||
* @param abortController
|
||||
* @param approvedTools
|
||||
* @param showModal
|
||||
* @param allowAllMCPPermissions
|
||||
*/
|
||||
export const postMessageProcessing = async (
|
||||
calls: ChatCompletionMessageToolCall[],
|
||||
|
||||
@ -2,8 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createFileRoute, useParams } from '@tanstack/react-router'
|
||||
import { UIEventHandler } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ArrowDown } from 'lucide-react'
|
||||
import { Play } from 'lucide-react'
|
||||
|
||||
import HeaderPage from '@/containers/HeaderPage'
|
||||
import { useThreads } from '@/hooks/useThreads'
|
||||
@ -18,7 +20,9 @@ import { useAppState } from '@/hooks/useAppState'
|
||||
import DropdownAssistant from '@/containers/DropdownAssistant'
|
||||
import { useAssistant } from '@/hooks/useAssistant'
|
||||
import { useAppearance } from '@/hooks/useAppearance'
|
||||
import { ContentType, ThreadMessage } from '@janhq/core'
|
||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||
import { useChat } from '@/hooks/useChat'
|
||||
import { useSmallScreen } from '@/hooks/useMediaQuery'
|
||||
|
||||
// as route.threadsDetail
|
||||
@ -38,6 +42,7 @@ function ThreadDetail() {
|
||||
const { setMessages } = useMessages()
|
||||
const { streamingContent } = useAppState()
|
||||
const { appMainViewBgColor, chatWidth } = useAppearance()
|
||||
const { sendMessage } = useChat()
|
||||
const isSmallScreen = useSmallScreen()
|
||||
|
||||
const { messages } = useMessages(
|
||||
@ -180,6 +185,26 @@ function ThreadDetail() {
|
||||
lastScrollTopRef.current = scrollTop
|
||||
}
|
||||
|
||||
const updateMessage = (item: ThreadMessage, message: string) => {
|
||||
const newMessages: ThreadMessage[] = messages.map((m) => {
|
||||
if (m.id === item.id) {
|
||||
const msg: ThreadMessage = cloneDeep(m)
|
||||
msg.content = [
|
||||
{
|
||||
type: ContentType.Text,
|
||||
text: {
|
||||
value: message,
|
||||
annotations: m.content[0].text?.annotations ?? [],
|
||||
},
|
||||
},
|
||||
]
|
||||
return msg
|
||||
}
|
||||
return m
|
||||
})
|
||||
setMessages(threadId, newMessages)
|
||||
}
|
||||
|
||||
// Use a shorter debounce time for more responsive scrolling
|
||||
const debouncedScroll = debounce(handleDOMScroll)
|
||||
|
||||
@ -193,10 +218,22 @@ function ThreadDetail() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
// used when there is a sent/added user message and no assistant message (error or manual deletion)
|
||||
const generateAIResponse = () => {
|
||||
const latestUserMessage = messages[messages.length - 1]
|
||||
if (latestUserMessage?.content?.[0]?.text?.value) {
|
||||
sendMessage(latestUserMessage.content[0].text.value, false)
|
||||
}
|
||||
}
|
||||
|
||||
const threadModel = useMemo(() => thread?.model, [thread])
|
||||
|
||||
if (!messages || !threadModel) return null
|
||||
|
||||
const showScrollToBottomBtn = !isAtBottom && hasScrollbar
|
||||
const showGenerateAIResponseBtn =
|
||||
messages[messages.length - 1]?.role === 'user' && !streamingContent
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<HeaderPage>
|
||||
@ -243,6 +280,7 @@ function ThreadDetail() {
|
||||
))
|
||||
}
|
||||
index={index}
|
||||
updateMessage={updateMessage}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@ -266,19 +304,31 @@ function ThreadDetail() {
|
||||
appMainViewBgColor.a === 1
|
||||
? 'from-main-view/20 bg-gradient-to-b to-main-view backdrop-blur'
|
||||
: 'bg-transparent',
|
||||
!isAtBottom && hasScrollbar && 'visibility-visible opacity-100'
|
||||
(showScrollToBottomBtn || showGenerateAIResponseBtn) &&
|
||||
'visibility-visible opacity-100'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
|
||||
onClick={() => {
|
||||
scrollToBottom(true)
|
||||
setIsUserScrolling(false)
|
||||
}}
|
||||
>
|
||||
<p className="text-xs">{t('scrollToBottom')}</p>
|
||||
<ArrowDown size={12} />
|
||||
</div>
|
||||
{showScrollToBottomBtn && (
|
||||
<div
|
||||
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
|
||||
onClick={() => {
|
||||
scrollToBottom(true)
|
||||
setIsUserScrolling(false)
|
||||
}}
|
||||
>
|
||||
<p className="text-xs">{t('scrollToBottom')}</p>
|
||||
<ArrowDown size={12} />
|
||||
</div>
|
||||
)}
|
||||
{showGenerateAIResponseBtn && (
|
||||
<div
|
||||
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
|
||||
onClick={generateAIResponse}
|
||||
>
|
||||
<p className="text-xs">{t('Generate AI Response')}</p>
|
||||
<Play size={12} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ChatInput model={threadModel} />
|
||||
</div>
|
||||
|
||||
@ -72,5 +72,13 @@ export default defineConfig(({ mode }) => {
|
||||
ignored: ['**/src-tauri/**'],
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['json', 'lcov'],
|
||||
reportsDirectory: '../coverage/vitest',
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user