chore: resolve conflict
158
.github/workflows/build-app.yml
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
name: Jan Build MacOS App
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*.*.*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
environment: production
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install jq
|
||||||
|
uses: dcarbone/install-jq-action@v2.0.1
|
||||||
|
|
||||||
|
- name: Get tag
|
||||||
|
id: tag
|
||||||
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
|
||||||
|
- name: Update app version base on tag
|
||||||
|
run: |
|
||||||
|
if [[ ! "${VERSION_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Tag is not valid!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
env:
|
||||||
|
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Get Cer for code signing
|
||||||
|
run: base64 -d <<< "$CODE_SIGN_P12_BASE64" > /tmp/codesign.p12
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CODE_SIGN_P12_BASE64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
|
||||||
|
|
||||||
|
- uses: apple-actions/import-codesign-certs@v2
|
||||||
|
with:
|
||||||
|
p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
|
||||||
|
p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Install yarn dependencies
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
yarn build:plugins-darwin
|
||||||
|
|
||||||
|
- name: Build and publish app
|
||||||
|
run: |
|
||||||
|
yarn build:publish-darwin
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CSC_LINK: "/tmp/codesign.p12"
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: "true"
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
|
|
||||||
|
build-windows-x64:
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install jq
|
||||||
|
uses: dcarbone/install-jq-action@v2.0.1
|
||||||
|
|
||||||
|
- name: Get tag
|
||||||
|
id: tag
|
||||||
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
|
||||||
|
- name: Update app version base on tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [[ ! "${VERSION_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Tag is not valid!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
env:
|
||||||
|
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Install yarn dependencies
|
||||||
|
run: |
|
||||||
|
yarn config set network-timeout 300000
|
||||||
|
yarn install
|
||||||
|
yarn build:plugins
|
||||||
|
|
||||||
|
- name: Build and publish app
|
||||||
|
run: |
|
||||||
|
yarn build:publish-win32
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
build-linux-x64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: production
|
||||||
|
env:
|
||||||
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install jq
|
||||||
|
uses: dcarbone/install-jq-action@v2.0.1
|
||||||
|
|
||||||
|
- name: Install Snapcraft
|
||||||
|
uses: samuelmeuli/action-snapcraft@v2
|
||||||
|
|
||||||
|
- name: Get tag
|
||||||
|
id: tag
|
||||||
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
|
||||||
|
- name: Update app version base on tag
|
||||||
|
run: |
|
||||||
|
if [[ ! "${VERSION_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Tag is not valid!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
|
mv /tmp/package.json electron/package.json
|
||||||
|
env:
|
||||||
|
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Install yarn dependencies
|
||||||
|
run: |
|
||||||
|
yarn config set network-timeout 300000
|
||||||
|
yarn install
|
||||||
|
yarn build:plugins
|
||||||
|
|
||||||
|
- name: Build and publish app
|
||||||
|
run: |
|
||||||
|
yarn build:publish-linux
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
2
.github/workflows/deploy-jan-docs.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
run: yarn install
|
run: yarn install
|
||||||
working-directory: docs
|
working-directory: docs
|
||||||
- name: Build website
|
- name: Build website
|
||||||
run: yarn build
|
run: sed -i '/process.env.DEBUG = namespaces;/c\// process.env.DEBUG = namespaces;' ./node_modules/debug/src/node.js && yarn build
|
||||||
working-directory: docs
|
working-directory: docs
|
||||||
|
|
||||||
- name: Add Custome Domain file
|
- name: Add Custome Domain file
|
||||||
|
|||||||
29
.github/workflows/jan-docs-test.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Jan Docs Test Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- '.github/workflows/deploy-jan-docs.yml'
|
||||||
|
- '.github/workflows/jan-docs-test.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Test Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: './docs/yarn.lock'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
working-directory: docs
|
||||||
|
- name: Test Build Command
|
||||||
|
run: sed -i '/process.env.DEBUG = namespaces;/c\// process.env.DEBUG = namespaces;' ./node_modules/debug/src/node.js && yarn build
|
||||||
|
working-directory: docs
|
||||||
88
.github/workflows/linter-and-test.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
name: Linter & Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'electron/**'
|
||||||
|
- .github/workflows/linter-and-test.yml
|
||||||
|
- 'web/**'
|
||||||
|
- 'package.json'
|
||||||
|
- 'node_modules/**'
|
||||||
|
- 'yarn.lock'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'electron/**'
|
||||||
|
- .github/workflows/linter-and-test.yml
|
||||||
|
- 'web/**'
|
||||||
|
- 'package.json'
|
||||||
|
- 'node_modules/**'
|
||||||
|
- 'yarn.lock'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-on-macos:
|
||||||
|
runs-on: [self-hosted, macOS, macos-desktop]
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
run: |
|
||||||
|
yarn config set network-timeout 300000
|
||||||
|
yarn install
|
||||||
|
yarn lint
|
||||||
|
yarn build:plugins
|
||||||
|
yarn build
|
||||||
|
yarn test
|
||||||
|
env:
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||||
|
|
||||||
|
test-on-windows:
|
||||||
|
runs-on: [self-hosted, Windows, windows-desktop]
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
run: |
|
||||||
|
yarn config set network-timeout 300000
|
||||||
|
yarn install
|
||||||
|
yarn lint
|
||||||
|
yarn build:plugins
|
||||||
|
yarn build:win32
|
||||||
|
yarn test
|
||||||
|
|
||||||
|
test-on-ubuntu:
|
||||||
|
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
run: |
|
||||||
|
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||||
|
echo -e "Display ID: $DISPLAY"
|
||||||
|
yarn config set network-timeout 300000
|
||||||
|
yarn install
|
||||||
|
yarn lint
|
||||||
|
yarn build:plugins
|
||||||
|
yarn build:linux
|
||||||
|
yarn test
|
||||||
50
.github/workflows/macos-build-app.yml
vendored
@ -1,50 +0,0 @@
|
|||||||
name: Jan Build MacOS App
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags: ['v*.*.*']
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos-app:
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Getting the repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Installing node
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install jq
|
|
||||||
uses: dcarbone/install-jq-action@v2.0.1
|
|
||||||
|
|
||||||
- name: Get tag
|
|
||||||
id: tag
|
|
||||||
uses: dawidd6/action-get-tag@v1
|
|
||||||
|
|
||||||
- name: Update app version base on tag
|
|
||||||
run: |
|
|
||||||
if [[ ! "${VERSION_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo "Error: Tag is not valid!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
|
||||||
mv /tmp/package.json electron/package.json
|
|
||||||
env:
|
|
||||||
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Install yarn dependencies
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
yarn build:plugins
|
|
||||||
|
|
||||||
- name: Build and publish app
|
|
||||||
run: |
|
|
||||||
yarn build:publish
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
1
.gitignore
vendored
@ -5,7 +5,6 @@
|
|||||||
models/**
|
models/**
|
||||||
error.log
|
error.log
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
|
||||||
*.tgz
|
*.tgz
|
||||||
yarn.lock
|
yarn.lock
|
||||||
dist
|
dist
|
||||||
|
|||||||
82
README.md
@ -20,64 +20,75 @@
|
|||||||
|
|
||||||
> ⚠️ **Jan is currently in Development**: Expect breaking changes and bugs!
|
> ⚠️ **Jan is currently in Development**: Expect breaking changes and bugs!
|
||||||
|
|
||||||
Jan lets you run AI on your own hardware, with helpful tools to manage models and monitor your hardware performance.
|
**Use offline LLMs with your own data.** Run open source models like Llama2 or Falcon on your internal computers/servers.
|
||||||
|
|
||||||
In the background, Jan runs [Nitro](https://nitro.jan.ai), a C++ inference engine. It runs various model formats (GGUF/TensorRT) on various hardware (Mac M1/M2/Intel, Windows, Linux, and datacenter-grade Nvidia GPUs) with optional GPU acceleration.
|
**Jan runs on any hardware.** From PCs to multi-GPU clusters, Jan supports universal architectures:
|
||||||
|
|
||||||
> See the Nitro codebase at https://nitro.jan.ai.
|
- [x] Nvidia GPUs (fast)
|
||||||
|
- [x] Apple M-series (fast)
|
||||||
|
- [x] Apple Intel
|
||||||
|
- [x] Linux Debian
|
||||||
|
- [x] Windows x64
|
||||||
|
|
||||||
<!-- TODO: uncomment this later when we have this feature -->
|
> Download Jan at https://jan.ai/
|
||||||
<!-- Jan can be run as a server or cloud-native application for enterprise. We offer enterprise plugins for LDAP integration and Audit Logs. Contact us at [hello@jan.ai](mailto:hello@jan.ai) for more details. -->
|
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img style='border:1px solid #000000' src="https://github.com/janhq/jan/assets/69952136/1f9bb48c-2e70-4633-9f68-7881cd925972" alt="Jan Web GIF">
|
<img style='border:1px solid #000000' src="https://github.com/janhq/jan/assets/69952136/1db9c3d3-79b1-4988-afb5-afd4f4afd0d9" alt="Jan Web GIF">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
_Screenshot: Jan v0.1.3 on Mac M1 Pro, 16GB Sonoma_
|
||||||
|
|
||||||
## Quicklinks
|
## Quicklinks
|
||||||
|
|
||||||
- Developer documentation: https://jan.ai/docs (Work in Progress)
|
- [Developer docs](https://jan.ai/docs) (WIP)
|
||||||
- Desktop app: Download at https://jan.ai/
|
- Mobile App shell: [App Store](https://apps.apple.com/us/app/jan-on-device-ai-cloud-ais/id6449664703) | [Android](https://play.google.com/store/apps/details?id=com.jan.ai)
|
||||||
- Mobile app shell: Download via [App Store](https://apps.apple.com/us/app/jan-on-device-ai-cloud-ais/id6449664703) | [Android](https://play.google.com/store/apps/details?id=com.jan.ai)
|
- [Nitro Github](https://nitro.jan.ai): Jan's AI engine
|
||||||
- Nitro (C++ AI Engine): https://nitro.jan.ai
|
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
Jan supports core & 3rd party extensions:
|
Jan supports core & 3rd party extensions:
|
||||||
|
|
||||||
- [x] **LLM chat**: Self-hosted Llama2 and LLMs
|
- [x] **LLM chat**: Self-hosted Llama2 and LLMs
|
||||||
- [x] **Model Manager**: 1-click to install, swap, and delete models
|
- [x] **Model Manager**: 1-click to install, swap, and delete models with HuggingFace integration
|
||||||
- [x] **Storage**: Optionally store your conversation history and other data in SQLite/your storage of choice
|
- [x] **Storage**: Optionally save conversation history and other data in SQLite
|
||||||
- [ ] **3rd-party AIs**: Connect to ChatGPT, Claude via API Key (in progress)
|
- [ ] **3rd-party AIs**: Connect to ChatGPT, Claude via API Key (in progress)
|
||||||
- [ ] **Cross device support**: Mobile & Web support for custom shared servers (in progress)
|
- [ ] **Cross device support**: Mobile & Web support for custom shared servers (in progress)
|
||||||
- [ ] **File retrieval**: User can upload private and run a vectorDB (planned)
|
- [ ] **File retrieval**: User can chat with docs
|
||||||
- [ ] **Multi-user support**: Share a single server across a team/friends (planned)
|
- [ ] **Multi-user support**: Share a single server across a team/friends (planned)
|
||||||
- [ ] **Compliance**: Auditing and flagging features (planned)
|
- [ ] **Compliance**: Auditing and flagging features (planned)
|
||||||
|
|
||||||
## Hardware Support
|
## Nitro (Jan's AI engine)
|
||||||
|
|
||||||
Nitro provides both CPU and GPU support, via [llama.cpp](https://github.com/ggerganov/llama.cpp) and [TensorRT](https://github.com/NVIDIA/TensorRT), respectively.
|
In the background, Jan runs [Nitro](https://nitro.jan.ai), an open source, C++ inference engine. It runs various model formats (GGUF/TensorRT) on various hardware (Mac M1/M2/Intel, Windows, Linux, and datacenter-grade Nvidia GPUs) with optional GPU acceleration.
|
||||||
|
|
||||||
- [x] Nvidia GPUs (accelerated)
|
> See the open source Nitro codebase at https://nitro.jan.ai.
|
||||||
- [x] Apple M-series (accelerated)
|
|
||||||
- [x] Linux DEB
|
|
||||||
- [x] Windows x64
|
|
||||||
|
|
||||||
Not supported yet: Apple Intel, Linux RPM, Windows x86|ARM64, AMD ROCm
|
## Troubleshooting
|
||||||
|
As Jan is development mode, you might get stuck on a broken build.
|
||||||
|
|
||||||
> See [developer docs](https://docs.jan.ai/docs/) for detailed installation instructions.
|
To reset your installation:
|
||||||
|
|
||||||
|
1. Delete Jan Application from /Applications
|
||||||
|
|
||||||
|
1. Clear cache:
|
||||||
|
`rm -rf /Users/$(whoami)/Library/Application\ Support/jan-electron`
|
||||||
|
OR
|
||||||
|
`rm -rf /Users/$(whoami)/Library/Application\ Support/jan`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file
|
Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file
|
||||||
|
|
||||||
### Pre-requisites
|
### Pre-requisites
|
||||||
|
|
||||||
- node >= 20.0.0
|
- node >= 20.0.0
|
||||||
- yarn >= 1.22.0
|
- yarn >= 1.22.0
|
||||||
|
|
||||||
### Use as complete suite (in progress)
|
### Instructions
|
||||||
### For interactive development
|
|
||||||
|
|
||||||
Note: This instruction is tested on MacOS only.
|
Note: This instruction is tested on MacOS only.
|
||||||
|
|
||||||
@ -85,7 +96,7 @@ Note: This instruction is tested on MacOS only.
|
|||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
git checkout feature/hackathon-refactor-jan-into-electron-app
|
git checkout DESIRED_BRANCH
|
||||||
cd jan
|
cd jan
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -98,28 +109,29 @@ Note: This instruction is tested on MacOS only.
|
|||||||
yarn build:plugins
|
yarn build:plugins
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Run development and Using Jan Desktop**
|
3. **Run development and Using Jan Desktop**
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the development server and open the desktop app.
|
This will start the development server and open the desktop app.
|
||||||
In this step, there are a few notification about installing base plugin, just click `OK` and `Next` to continue.
|
In this step, there are a few notification about installing base plugin, just click `OK` and `Next` to continue.
|
||||||
|
|
||||||
### For production build
|
### For production build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Do step 1 and 2 in previous section
|
# Do step 1 and 2 in previous section
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:plugins
|
yarn build:plugins
|
||||||
|
|
||||||
# Build the app
|
# Build the app
|
||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder.
|
This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
29
adr/adr-003-jan-plugins.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# ADR 003: JAN PLUGINS
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- Oct 5th 2023: Initial draft
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Modular Architecture w/ Plugins:
|
||||||
|
|
||||||
|
- Jan will have an architecture similar to VSCode or k8Lens
|
||||||
|
- "Desktop Application" whose functionality can be extended thru plugins
|
||||||
|
- Jan's architecture will need to accomodate plugins for (a) Persistence(b) IAM(c) Teams and RBAC(d) Policy engines(e) "Apps" (i.e. higher-order business logic)(f) Themes (UI)
|
||||||
|
- Nitro's architecture will need to accomodate plugins for different "model backends"(a) llama.cpp(b) rkwk (and others)(c) 3rd-party AIs
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
What becomes easier or more difficult to do because of this change?
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
[Plugin APIs](./adr-003-jan-plugins.md)
|
||||||
37
adr/docs/adr-003-plugins.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
## JAN service & plugin APIs
|
||||||
|
|
||||||
|
Jan frontend components will communicate with plugin functions via Service Interfaces:
|
||||||
|
|
||||||
|
|
||||||
|
All of the available APIs are listed in [CoreService](../../web/shared/coreService.ts)
|
||||||
|
|
||||||
|
- Data Service:
|
||||||
|
- GET_CONVERSATIONS: retrieve all of the conversations
|
||||||
|
- CREATE_CONVERSATION: start a new conversation
|
||||||
|
- DELETE_CONVERSATION: delete an existing conversation
|
||||||
|
- GET_CONVERSATION_MESSAGES: retrieve a certain conversation messages
|
||||||
|
- CREATE_MESSAGE: store a new message (both sent & received)
|
||||||
|
- UPDATE_MESSAGE: update an existing message (streaming)
|
||||||
|
- STORE_MODEL: store new model information (when clicking download)
|
||||||
|
- UPDATE_FINISHED_DOWNLOAD: mark a model as downloaded
|
||||||
|
- GET_UNFINISHED_DOWNLOAD_MODELS: retrieve all unfinished downloading model (TBD)
|
||||||
|
- GET_FINISHED_DOWNLOAD_MODELS: retrieve all finished downloading model (TBD)
|
||||||
|
- DELETE_DOWNLOAD_MODEL: delete a model (TBD)
|
||||||
|
- GET_MODEL_BY_ID: retrieve model information by its ID
|
||||||
|
|
||||||
|
- Inference Service:
|
||||||
|
- INFERENCE_URL: retrieve inference endpoint served by plugin
|
||||||
|
- INIT_MODEL: runs a model
|
||||||
|
- STOP_MODEL: stop a running model
|
||||||
|
|
||||||
|
- Model Management Service: (TBD)
|
||||||
|
- GET_AVAILABLE_MODELS: retrieve available models (deprecate soon)
|
||||||
|
- GET_DOWNLOADED_MODELS: (deprecated)
|
||||||
|
- DELETE_MODEL: (deprecated)
|
||||||
|
- DOWNLOAD_MODEL: start to download a model
|
||||||
|
- SEARCH_MODELS: explore models with search query on HuggingFace (TBD)
|
||||||
|
|
||||||
|
- Monitoring service:
|
||||||
|
- GET_RESOURCES_INFORMATION: retrieve total & used memory information
|
||||||
|
- GET_CURRENT_LOAD_INFORMATION: retrieve CPU load information
|
||||||
|
|
||||||
BIN
adr/images/adr-003-01.png
Normal file
|
After Width: | Height: | Size: 335 KiB |
15166
docs/package-lock.json
generated
Normal file
@ -21,7 +21,9 @@
|
|||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
|
"axios": "^1.5.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"postcss": "^8.4.30",
|
"postcss": "^8.4.30",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "^1.3.5",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@ -30,7 +32,8 @@
|
|||||||
"tailwindcss": "^3.3.3"
|
"tailwindcss": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^2.4.3"
|
"@docusaurus/module-type-aliases": "2.4.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@ -1,28 +1,29 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const items = [
|
const systemsTemplate = [
|
||||||
{
|
{
|
||||||
name: "Download for Mac (M1/M2)",
|
name: "Download for Mac (M1/M2)",
|
||||||
href: "https://github.com/janhq/jan/releases/download/v0.1.2/Jan-0.1.2-arm64.dmg",
|
|
||||||
logo: require("@site/static/img/apple-logo-white.png").default,
|
logo: require("@site/static/img/apple-logo-white.png").default,
|
||||||
|
fileFormat: "{appname}-mac-arm64-{tag}.dmg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Download for Mac (Intel)",
|
name: "Download for Mac (Intel)",
|
||||||
href: "https://github.com/janhq/jan/releases/download/v0.1.2/Jan-0.1.2-arm64.dmg",
|
|
||||||
logo: require("@site/static/img/apple-logo-white.png").default,
|
logo: require("@site/static/img/apple-logo-white.png").default,
|
||||||
|
fileFormat: "{appname}-mac-x64-{tag}.dmg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Download for Windows",
|
name: "Download for Windows",
|
||||||
href: "https://static.vecteezy.com/system/resources/previews/004/243/615/non_2x/creative-coming-soon-teaser-background-free-vector.jpg",
|
|
||||||
logo: require("@site/static/img/windows-logo-white.png").default,
|
logo: require("@site/static/img/windows-logo-white.png").default,
|
||||||
|
fileFormat: "{appname}-win-x64-{tag}.exe",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Download for Linux",
|
name: "Download for Linux",
|
||||||
href: "https://static.vecteezy.com/system/resources/previews/004/243/615/non_2x/creative-coming-soon-teaser-background-free-vector.jpg",
|
|
||||||
logo: require("@site/static/img/linux-logo-white.png").default,
|
logo: require("@site/static/img/linux-logo-white.png").default,
|
||||||
|
fileFormat: "{appname}-linux-amd64-{tag}.deb",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -31,22 +32,81 @@ function classNames(...classes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Dropdown() {
|
export default function Dropdown() {
|
||||||
|
const [systems, setSystems] = useState(systemsTemplate);
|
||||||
|
const [defaultSystem, setDefaultSystem] = useState(systems[0]);
|
||||||
|
|
||||||
|
const getLatestReleaseInfo = async (repoOwner, repoName) => {
|
||||||
|
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractAppName = (fileName) => {
|
||||||
|
// Extract appname using a regex that matches the provided file formats
|
||||||
|
const regex = /^(.*?)-(?:mac|win|linux)-(?:arm64|x64|amd64)-.*$/;
|
||||||
|
const match = fileName.match(regex);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateDownloadLinks = async () => {
|
||||||
|
try {
|
||||||
|
const releaseInfo = await getLatestReleaseInfo("janhq", "jan");
|
||||||
|
|
||||||
|
// Extract appname from the first asset name
|
||||||
|
const firstAssetName = releaseInfo.assets[0].name;
|
||||||
|
const appname = extractAppName(firstAssetName);
|
||||||
|
|
||||||
|
if (!appname) {
|
||||||
|
console.error("Failed to extract appname from file name:", firstAssetName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove 'v' at the start of the tag_name
|
||||||
|
const tag = releaseInfo.tag_name.startsWith("v")
|
||||||
|
? releaseInfo.tag_name.substring(1)
|
||||||
|
: releaseInfo.tag_name;
|
||||||
|
|
||||||
|
const updatedSystems = systems.map((system) => {
|
||||||
|
const downloadUrl = system.fileFormat
|
||||||
|
.replace("{appname}", appname)
|
||||||
|
.replace("{tag}", tag);
|
||||||
|
return {
|
||||||
|
...system,
|
||||||
|
href: `https://github.com/janhq/jan/releases/download/${releaseInfo.tag_name}/${downloadUrl}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setSystems(updatedSystems);
|
||||||
|
setDefaultSystem(updatedSystems[0]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update download links:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDownloadLinks();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inline-flex align-items-stretch">
|
<div className="inline-flex align-items-stretch">
|
||||||
{/* TODO dynamically detect users OS through browser */}
|
|
||||||
<a
|
<a
|
||||||
className="cursor-pointer relative inline-flex items-center rounded-l-md border-0 px-3.5 py-2.5 text-base font-semibold text-white bg-indigo-600 dark:bg-indigo-500 hover:bg-indigo-500 dark:hover:bg-indigo-400 hover:text-white"
|
className="cursor-pointer relative inline-flex items-center rounded-l-md border-0 px-3.5 py-2.5 text-base font-semibold text-white bg-blue-600 dark:bg-blue-500 hover:bg-blue-500 dark:hover:bg-blue-400 hover:text-white"
|
||||||
href={items[0].href}
|
href={defaultSystem.href}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={require("@site/static/img/apple-logo-white.png").default}
|
src={require("@site/static/img/apple-logo-white.png").default}
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
className="h-5 mr-3 -mt-1"
|
className="h-5 mr-3 -mt-1"
|
||||||
/>
|
/>
|
||||||
Download for Mac (Silicon)
|
{defaultSystem.name}
|
||||||
</a>
|
</a>
|
||||||
<Menu as="div" className="relative -ml-px block">
|
<Menu as="div" className="relative -ml-px block">
|
||||||
<Menu.Button className="cursor-pointer relative inline-flex items-center rounded-r-md border-0 border-l border-gray-300 active:border-l active:border-white h-full text-white bg-indigo-600 dark:bg-indigo-500 hover:bg-indigo-500 dark:hover:bg-indigo-400">
|
<Menu.Button className="cursor-pointer relative inline-flex items-center rounded-r-md border-0 border-l border-gray-300 active:border-l active:border-white h-full text-white bg-blue-600 dark:bg-blue-500 hover:bg-blue-500 dark:hover:bg-blue-400">
|
||||||
<span className="sr-only">Open OS options</span>
|
<span className="sr-only">Open OS options</span>
|
||||||
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
@ -59,26 +119,26 @@ export default function Dropdown() {
|
|||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-72 text-left origin-top-right rounded-md bg-indigo-600 dark:bg-indigo-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-72 text-left origin-top-right rounded-md bg-blue-600 dark:bg-blue-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
{items.map((item) => (
|
{systems.map((system) => (
|
||||||
<Menu.Item key={item.name}>
|
<Menu.Item key={system.name}>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<a
|
<a
|
||||||
href={item.href}
|
href={system.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
active
|
active
|
||||||
? "bg-indigo-500 dark:hover:bg-indigo-400 hover:text-white"
|
? "bg-blue-500 dark:hover:bg-blue-400 hover:text-white"
|
||||||
: "text-white",
|
: "text-white",
|
||||||
"block px-4 py-2"
|
"block px-4 py-2"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={item.logo}
|
src={system.logo}
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
className="w-3 mr-3 -mt-1"
|
className="w-3 mr-3 -mt-1"
|
||||||
/>
|
/>
|
||||||
{item.name}
|
{system.name}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
LockClosedIcon,
|
LockClosedIcon,
|
||||||
} from "@heroicons/react/20/solid";
|
} from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
const features = [
|
const systems = [
|
||||||
{
|
{
|
||||||
name: "Mac",
|
name: "Mac",
|
||||||
description:
|
description:
|
||||||
@ -47,20 +47,20 @@ export default function HomepageDownloads() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
|
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
|
||||||
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none lg:grid-cols-3">
|
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none lg:grid-cols-3">
|
||||||
{features.map((feature) => (
|
{systems.map((system) => (
|
||||||
<div key={feature.name} className="flex flex-col">
|
<div key={system.name} className="flex flex-col">
|
||||||
<dt className="flex items-center gap-x-3 text-base font-semibold leading-7 text-gray-900 dark: text-white">
|
<dt className="flex items-center gap-x-3 text-base font-semibold leading-7 text-gray-900 dark: text-white">
|
||||||
<feature.icon
|
<system.icon
|
||||||
className="h-5 w-5 flex-none text-indigo-600 dark:text-indigo-400"
|
className="h-5 w-5 flex-none text-indigo-600 dark:text-indigo-400"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{feature.name}
|
{system.name}
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-4 flex flex-auto flex-col text-base leading-7 text-gray-600 dark:text-gray-300">
|
<dd className="mt-4 flex flex-auto flex-col text-base leading-7 text-gray-600 dark:text-gray-300">
|
||||||
<p className="flex-auto">{feature.description}</p>
|
<p className="flex-auto">{system.description}</p>
|
||||||
<p className="mt-6">
|
<p className="mt-6">
|
||||||
<a
|
<a
|
||||||
href={feature.href}
|
href={system.href}
|
||||||
className="text-sm font-semibold leading-6 text-indigo-600 dark:text-indigo-400"
|
className="text-sm font-semibold leading-6 text-indigo-600 dark:text-indigo-400"
|
||||||
>
|
>
|
||||||
Learn more <span aria-hidden="true">→</span>
|
Learn more <span aria-hidden="true">→</span>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default function HomepageHero() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-gray-900">
|
<div className="bg-white dark:bg-gray-900">
|
||||||
<div className="relative isolate pt-14">
|
<div className="relative isolate md:pt-14 pt-0">
|
||||||
{/* Background top gradient styling */}
|
{/* Background top gradient styling */}
|
||||||
{colorMode === "dark" ? (
|
{colorMode === "dark" ? (
|
||||||
<div
|
<div
|
||||||
@ -39,7 +39,7 @@ export default function HomepageHero() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main hero block */}
|
{/* Main hero block */}
|
||||||
<div className="py-24 sm:py-32 lg:pb-40">
|
<div className="py-24 lg:pb-40 animate-in fade-in zoom-in-50 duration-1000 ">
|
||||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||||
{/* Hero text and buttons */}
|
{/* Hero text and buttons */}
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
<div className="mx-auto max-w-2xl text-center">
|
||||||
@ -60,7 +60,7 @@ export default function HomepageHero() {
|
|||||||
<Dropdown />
|
<Dropdown />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="cursor-pointer relative inline-flex items-center rounded px-3.5 py-2 dark:py-2.5 text-base font-semibold text-indigo-600 bg-white border-indigo-600 dark:border-0 hover:bg-indigo-600 dark:hover:bg-indigo-500 hover:text-white"
|
className="cursor-pointer relative inline-flex items-center rounded px-3.5 py-2 dark:py-2.5 text-base font-semibold text-blue-600 bg-white border-blue-600 dark:border-0 hover:bg-blue-600 dark:hover:bg-blue-500 hover:text-white"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
window.open(
|
window.open(
|
||||||
"https://github.com/janhq/jan",
|
"https://github.com/janhq/jan",
|
||||||
@ -79,8 +79,10 @@ export default function HomepageHero() {
|
|||||||
src={
|
src={
|
||||||
colorMode === "dark"
|
colorMode === "dark"
|
||||||
? // TODO replace with darkmode image
|
? // TODO replace with darkmode image
|
||||||
require("@site/static/img/desktop-llm-chat.png").default
|
require("@site/static/img/desktop-llm-chat-dark.png")
|
||||||
: require("@site/static/img/desktop-llm-chat.png").default
|
.default
|
||||||
|
: require("@site/static/img/desktop-llm-chat-light.png")
|
||||||
|
.default
|
||||||
}
|
}
|
||||||
alt="App screenshot"
|
alt="App screenshot"
|
||||||
width={2432}
|
width={2432}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export default function HomepageSectionOne() {
|
|||||||
/>
|
/>
|
||||||
{feature.name}
|
{feature.name}
|
||||||
</dt>{" "}
|
</dt>{" "}
|
||||||
<dd className="inline">{feature.description}</dd>
|
<dt>{feature.description}</dt>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</dl>
|
</dl>
|
||||||
@ -71,7 +71,7 @@ export default function HomepageSectionOne() {
|
|||||||
? // TODO replace with darkmode image
|
? // TODO replace with darkmode image
|
||||||
require("@site/static/img/desktop-explore-models-dark.png")
|
require("@site/static/img/desktop-explore-models-dark.png")
|
||||||
.default
|
.default
|
||||||
: require("@site/static/img/desktop-explore-models.png")
|
: require("@site/static/img/desktop-explore-models-light.png")
|
||||||
.default
|
.default
|
||||||
}
|
}
|
||||||
alt="Product screenshot"
|
alt="Product screenshot"
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export default function sectionTwo() {
|
|||||||
/>
|
/>
|
||||||
{feature.name}
|
{feature.name}
|
||||||
</dt>{" "}
|
</dt>{" "}
|
||||||
<dd className="inline">{feature.description}</dd>
|
<dt>{feature.description}</dt>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</dl>
|
</dl>
|
||||||
@ -68,8 +68,8 @@ export default function sectionTwo() {
|
|||||||
src={
|
src={
|
||||||
colorMode === "dark"
|
colorMode === "dark"
|
||||||
? // TODO replace with darkmode image
|
? // TODO replace with darkmode image
|
||||||
require("@site/static/img/desktop-model-settings.png").default
|
require("@site/static/img/desktop-model-settings-dark.png").default
|
||||||
: require("@site/static/img/desktop-model-settings.png").default
|
: require("@site/static/img/desktop-model-settings-light.png").default
|
||||||
}
|
}
|
||||||
alt="Product screenshot"
|
alt="Product screenshot"
|
||||||
className="w-[48rem] max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-[57rem] md:-ml-4 lg:-ml-0"
|
className="w-[48rem] max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-[57rem] md:-ml-4 lg:-ml-0"
|
||||||
|
|||||||
@ -12,26 +12,35 @@
|
|||||||
Full list of Infima variables: https://github.com/facebook/docusaurus/issues/3955#issuecomment-1521944593
|
Full list of Infima variables: https://github.com/facebook/docusaurus/issues/3955#issuecomment-1521944593
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
--ifm-color-primary: #2e8555;
|
--ifm-color-primary: #2563EB; /* New Primary Blue */
|
||||||
--ifm-color-primary-dark: #29784c;
|
--ifm-color-primary-dark: #204FCF; /* Darker Blue */
|
||||||
--ifm-color-primary-darker: #277148;
|
--ifm-color-primary-darker: #1B45B7; /* Even Darker Blue */
|
||||||
--ifm-color-primary-darkest: #205d3b;
|
--ifm-color-primary-darkest: #163C9D; /* Darkest Blue */
|
||||||
--ifm-color-primary-light: #33925d;
|
--ifm-color-primary-light: #2974FF; /* Light Blue */
|
||||||
--ifm-color-primary-lighter: #359962;
|
--ifm-color-primary-lighter: #3280FF; /* Lighter Blue */
|
||||||
--ifm-color-primary-lightest: #3cad6e;
|
--ifm-color-primary-lightest: #3A8BFF; /* Lightest Blue */
|
||||||
--ifm-code-font-size: 95%;
|
--ifm-code-font-size: 95%;
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--ifm-color-primary: #25c2a0;
|
--ifm-color-primary: #ffffff; /* New Primary Blue */
|
||||||
--ifm-color-primary-dark: #21af90;
|
--ifm-color-primary-dark: #204FCF; /* Darker Blue */
|
||||||
--ifm-color-primary-darker: #1fa588;
|
--ifm-color-primary-darker: #1B45B7; /* Even Darker Blue */
|
||||||
--ifm-color-primary-darkest: #1a8870;
|
--ifm-color-primary-darkest: #163C9D; /* Darkest Blue */
|
||||||
--ifm-color-primary-light: #29d5b0;
|
--ifm-color-primary-light: #2974FF; /* Light Blue */
|
||||||
--ifm-color-primary-lighter: #32d8b4;
|
--ifm-color-primary-lighter: #3280FF; /* Lighter Blue */
|
||||||
--ifm-color-primary-lightest: #4fddbf;
|
--ifm-color-primary-lightest: #3A8BFF; /* Lightest Blue */
|
||||||
--ifm-navbar-background-color: rgba(15, 23, 42);
|
|
||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer.footer--dark {
|
||||||
|
--ifm-footer-background-color: #1a212f;
|
||||||
|
--ifm-footer-color: var(--ifm-footer-link-color);
|
||||||
|
--ifm-footer-link-color: var(--ifm-color-secondary);
|
||||||
|
--ifm-footer-title-color: var(--ifm-color-white);
|
||||||
|
|
||||||
|
background-color: var(--ifm-footer-background-color);
|
||||||
|
color: var(--ifm-footer-color)
|
||||||
|
}
|
||||||
BIN
docs/static/img/desktop-explore-models-dark.png
vendored
|
Before Width: | Height: | Size: 389 KiB After Width: | Height: | Size: 434 KiB |
BIN
docs/static/img/desktop-explore-models-light.png
vendored
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
docs/static/img/desktop-explore-models.png
vendored
|
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 129 KiB |
BIN
docs/static/img/desktop-llm-chat-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
docs/static/img/desktop-llm-chat-light.png
vendored
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
docs/static/img/desktop-model-settings-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 186 KiB |
BIN
docs/static/img/desktop-model-settings-light.png
vendored
Normal file
|
After Width: | Height: | Size: 192 KiB |
@ -9,5 +9,7 @@ module.exports = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [
|
||||||
|
require("tailwindcss-animate"),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
2729
docs/yarn.lock
44
electron/.eslintrc.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"react/prop-types": "off", // In favor of strong typing - no need to dedupe
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
createClass: "createReactClass", // Regex for Component Factory to use,
|
||||||
|
// default to "createReactClass"
|
||||||
|
pragma: "React", // Pragma to use, default to "React"
|
||||||
|
version: "detect", // React version. "detect" automatically picks the version you have installed.
|
||||||
|
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
|
||||||
|
// default to latest and warns if missing
|
||||||
|
// It will default to "detect" in the future
|
||||||
|
},
|
||||||
|
linkComponents: [
|
||||||
|
// Components used as alternatives to <a> for linking, eg. <Link to={ url } />
|
||||||
|
"Hyperlink",
|
||||||
|
{ name: "Link", linkAttribute: "to" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ignorePatterns: [
|
||||||
|
"build",
|
||||||
|
"renderer",
|
||||||
|
"node_modules",
|
||||||
|
"core/plugins",
|
||||||
|
"core/**/*.test.js",
|
||||||
|
],
|
||||||
|
};
|
||||||
5
electron/auto-sign.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DEVELOPER_ID="Developer ID Application: Eigenvector Pte Ltd"
|
||||||
|
|
||||||
|
find electron -type f -perm +111 -exec codesign -s "Developer ID Application: Eigenvector Pte Ltd (YT49P7GXG4)" --options=runtime {} \;
|
||||||
@ -16,11 +16,23 @@ class Plugin {
|
|||||||
/** @type {boolean} Whether this plugin should be activated when its activation points are triggered. */
|
/** @type {boolean} Whether this plugin should be activated when its activation points are triggered. */
|
||||||
active
|
active
|
||||||
|
|
||||||
constructor(name, url, activationPoints, active) {
|
/** @type {string} Plugin's description. */
|
||||||
|
description
|
||||||
|
|
||||||
|
/** @type {string} Plugin's version. */
|
||||||
|
version
|
||||||
|
|
||||||
|
/** @type {string} Plugin's logo. */
|
||||||
|
icon
|
||||||
|
|
||||||
|
constructor(name, url, activationPoints, active, description, version, icon) {
|
||||||
this.name = name
|
this.name = name
|
||||||
this.url = url
|
this.url = url
|
||||||
this.activationPoints = activationPoints
|
this.activationPoints = activationPoints
|
||||||
this.active = active
|
this.active = active
|
||||||
|
this.description = description
|
||||||
|
this.version = version
|
||||||
|
this.icon = icon
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export async function install(plugins) {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const plgList = await window.pluggableElectronIpc.install(plugins);
|
const plgList = await window.pluggableElectronIpc.install(plugins);
|
||||||
if (plgList.cancelled) return false;
|
if (plgList.cancelled) return false;
|
||||||
return plgList.map((plg) => {
|
return plgList.map((plg) => {
|
||||||
@ -50,6 +51,7 @@ export function uninstall(plugins, reload = true) {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
return window.pluggableElectronIpc.uninstall(plugins, reload);
|
return window.pluggableElectronIpc.uninstall(plugins, reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ export async function getActive() {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const plgList = await window.pluggableElectronIpc.getActive();
|
const plgList = await window.pluggableElectronIpc.getActive();
|
||||||
return plgList.map(
|
return plgList.map(
|
||||||
(plugin) =>
|
(plugin) =>
|
||||||
@ -69,7 +72,10 @@ export async function getActive() {
|
|||||||
plugin.name,
|
plugin.name,
|
||||||
plugin.url,
|
plugin.url,
|
||||||
plugin.activationPoints,
|
plugin.activationPoints,
|
||||||
plugin.active
|
plugin.active,
|
||||||
|
plugin.description,
|
||||||
|
plugin.version,
|
||||||
|
plugin.icon
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -83,6 +89,7 @@ export async function registerActive() {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const plgList = await window.pluggableElectronIpc.getActive();
|
const plgList = await window.pluggableElectronIpc.getActive();
|
||||||
plgList.forEach((plugin) =>
|
plgList.forEach((plugin) =>
|
||||||
register(
|
register(
|
||||||
@ -107,6 +114,7 @@ export async function update(plugins, reload = true) {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const plgList = await window.pluggableElectronIpc.update(plugins, reload);
|
const plgList = await window.pluggableElectronIpc.update(plugins, reload);
|
||||||
return plgList.map(
|
return plgList.map(
|
||||||
(plugin) =>
|
(plugin) =>
|
||||||
@ -129,6 +137,7 @@ export function updatesAvailable(plugin) {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
return window.pluggableElectronIpc.updatesAvailable(plugin);
|
return window.pluggableElectronIpc.updatesAvailable(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +152,7 @@ export async function toggleActive(plugin, active) {
|
|||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
const plg = await window.pluggableElectronIpc.toggleActive(plugin, active);
|
const plg = await window.pluggableElectronIpc.toggleActive(plugin, active);
|
||||||
return new Plugin(plg.name, plg.url, plg.activationPoints, plg.active);
|
return new Plugin(plg.name, plg.url, plg.activationPoints, plg.active);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export * as activationPoints from "./activation-manager.js";
|
|||||||
export * as plugins from "./facade.js";
|
export * as plugins from "./facade.js";
|
||||||
export { default as ExtensionPoint } from "./ExtensionPoint.js";
|
export { default as ExtensionPoint } from "./ExtensionPoint.js";
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
if (typeof window !== "undefined" && !window.pluggableElectronIpc)
|
if (typeof window !== "undefined" && !window.pluggableElectronIpc)
|
||||||
console.warn(
|
console.warn(
|
||||||
"Facade is not registered in preload. Facade functions will throw an error if used."
|
"Facade is not registered in preload. Facade functions will throw an error if used."
|
||||||
|
|||||||
@ -1,30 +1,32 @@
|
|||||||
import { ipcRenderer, contextBridge } from "electron"
|
const { ipcRenderer, contextBridge } = require("electron");
|
||||||
|
|
||||||
export default function useFacade() {
|
function useFacade() {
|
||||||
const interfaces = {
|
const interfaces = {
|
||||||
install(plugins) {
|
install(plugins) {
|
||||||
return ipcRenderer.invoke('pluggable:install', plugins)
|
return ipcRenderer.invoke("pluggable:install", plugins);
|
||||||
},
|
},
|
||||||
uninstall(plugins, reload) {
|
uninstall(plugins, reload) {
|
||||||
return ipcRenderer.invoke('pluggable:uninstall', plugins, reload)
|
return ipcRenderer.invoke("pluggable:uninstall", plugins, reload);
|
||||||
},
|
},
|
||||||
getActive() {
|
getActive() {
|
||||||
return ipcRenderer.invoke('pluggable:getActivePlugins')
|
return ipcRenderer.invoke("pluggable:getActivePlugins");
|
||||||
},
|
},
|
||||||
update(plugins, reload) {
|
update(plugins, reload) {
|
||||||
return ipcRenderer.invoke('pluggable:update', plugins, reload)
|
return ipcRenderer.invoke("pluggable:update", plugins, reload);
|
||||||
},
|
},
|
||||||
updatesAvailable(plugin) {
|
updatesAvailable(plugin) {
|
||||||
return ipcRenderer.invoke('pluggable:updatesAvailable', plugin)
|
return ipcRenderer.invoke("pluggable:updatesAvailable", plugin);
|
||||||
},
|
},
|
||||||
toggleActive(plugin, active) {
|
toggleActive(plugin, active) {
|
||||||
return ipcRenderer.invoke('pluggable:togglePluginActive', plugin, active)
|
return ipcRenderer.invoke("pluggable:togglePluginActive", plugin, active);
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
if (contextBridge) {
|
if (contextBridge) {
|
||||||
contextBridge.exposeInMainWorld('pluggableElectronIpc', interfaces)
|
contextBridge.exposeInMainWorld("pluggableElectronIpc", interfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces
|
return interfaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = useFacade;
|
||||||
|
|||||||
@ -18,6 +18,8 @@ class Plugin {
|
|||||||
* @property {string} version Version of the package as defined in the manifest.
|
* @property {string} version Version of the package as defined in the manifest.
|
||||||
* @property {Array<string>} activationPoints List of {@link ./Execution-API#activationPoints|activation points}.
|
* @property {Array<string>} activationPoints List of {@link ./Execution-API#activationPoints|activation points}.
|
||||||
* @property {string} main The entry point as defined in the main entry of the manifest.
|
* @property {string} main The entry point as defined in the main entry of the manifest.
|
||||||
|
* @property {string} description The description of plugin as defined in the manifest.
|
||||||
|
* @property {string} icon The icon of plugin as defined in the manifest.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
@ -75,6 +77,8 @@ class Plugin {
|
|||||||
this.version = mnf.version
|
this.version = mnf.version
|
||||||
this.activationPoints = mnf.activationPoints || null
|
this.activationPoints = mnf.activationPoints || null
|
||||||
this.main = mnf.main
|
this.main = mnf.main
|
||||||
|
this.description = mnf.description
|
||||||
|
this.icon = mnf.icon
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Package ${this.origin} does not contain a valid manifest: ${error}`)
|
throw new Error(`Package ${this.origin} does not contain a valid manifest: ${error}`)
|
||||||
|
|||||||
@ -61,10 +61,6 @@ function init() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const stmt = db.prepare(
|
|
||||||
"INSERT INTO conversations (name, model_id, image, message) VALUES (?, ?, ?, ?)"
|
|
||||||
);
|
|
||||||
stmt.finalize();
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +164,7 @@ function getFinishedDownloadModels() {
|
|||||||
|
|
||||||
const query = `SELECT * FROM models WHERE finish_download_at != -1 ORDER BY finish_download_at DESC`;
|
const query = `SELECT * FROM models WHERE finish_download_at != -1 ORDER BY finish_download_at DESC`;
|
||||||
db.all(query, (err: Error, row: any) => {
|
db.all(query, (err: Error, row: any) => {
|
||||||
res(row);
|
res(row?.map((item: any) => parseToProduct(item)) ?? []);
|
||||||
});
|
});
|
||||||
db.close();
|
db.close();
|
||||||
});
|
});
|
||||||
@ -184,6 +180,7 @@ function deleteDownloadModel(modelId: string) {
|
|||||||
const stmt = db.prepare("DELETE FROM models WHERE id = ?");
|
const stmt = db.prepare("DELETE FROM models WHERE id = ?");
|
||||||
stmt.run(modelId);
|
stmt.run(modelId);
|
||||||
stmt.finalize();
|
stmt.finalize();
|
||||||
|
res(modelId);
|
||||||
});
|
});
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
@ -352,7 +349,7 @@ function deleteConversation(id: any) {
|
|||||||
);
|
);
|
||||||
deleteMessages.run(id);
|
deleteMessages.run(id);
|
||||||
deleteMessages.finalize();
|
deleteMessages.finalize();
|
||||||
res([]);
|
res(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
@ -373,6 +370,31 @@ function getConversationMessages(conversation_id: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseToProduct(row: any) {
|
||||||
|
const product = {
|
||||||
|
id: row.id,
|
||||||
|
slug: row.slug,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
avatarUrl: row.avatar_url,
|
||||||
|
longDescription: row.long_description,
|
||||||
|
technicalDescription: row.technical_description,
|
||||||
|
author: row.author,
|
||||||
|
version: row.version,
|
||||||
|
modelUrl: row.model_url,
|
||||||
|
nsfw: row.nsfw,
|
||||||
|
greeting: row.greeting,
|
||||||
|
type: row.type,
|
||||||
|
inputs: row.inputs,
|
||||||
|
outputs: row.outputs,
|
||||||
|
createdAt: new Date(row.created_at),
|
||||||
|
updatedAt: new Date(row.updated_at),
|
||||||
|
fileName: row.file_name,
|
||||||
|
downloadUrl: row.download_url,
|
||||||
|
};
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init,
|
init,
|
||||||
getConversations,
|
getConversations,
|
||||||
|
|||||||
5606
electron/core/plugins/data-plugin/package-lock.json
generated
Normal file
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "data-plugin",
|
"name": "data-plugin",
|
||||||
"version": "2.1.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Jan Database Plugin efficiently stores conversation and model data using SQLite, providing accessible data management",
|
||||||
|
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/circle-stack.svg",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"author": "Jan",
|
"author": "Jan",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -10,8 +11,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -b . && webpack --config webpack.config.js",
|
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||||
"build:package": "rimraf ./data-plugin*.tgz && npm run build && npm pack",
|
"postinstall": "rimraf ./data-plugin*.tgz && node-pre-gyp install --directory=./node_modules/sqlite3 --target_platform=darwin --target_libc=unknown --target_arch=x64 && node-pre-gyp install --directory=./node_modules/sqlite3 --target_platform=darwin --target_libc=unknown --target_arch=arm64 && node-pre-gyp install --directory=./node_modules/sqlite3 --target_platform=linux --target_libc=glibc --target_arch=x64 && node-pre-gyp install --directory=./node_modules/sqlite3 --target_platform=linux --target_libc=musl --target_arch=x64 && node-pre-gyp install --directory=./node_modules/sqlite3 --target_platform=win32 --target_libc=unknown --target_arch=x64 && npm run build",
|
||||||
"build:publish": "npm run build:package && cpx *.tgz ../../pre-install"
|
"build:publish": "npm pack && cpx *.tgz ../../pre-install"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
@ -36,6 +37,7 @@
|
|||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"node-pre-gyp": "^0.17.0",
|
||||||
"sqlite3": "^5.1.6"
|
"sqlite3": "^5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,15 +13,20 @@ const dispose = async () =>
|
|||||||
new Promise(async (resolve) => {
|
new Promise(async (resolve) => {
|
||||||
if (window.electronAPI) {
|
if (window.electronAPI) {
|
||||||
window.electronAPI
|
window.electronAPI
|
||||||
.invokePluginFunc(MODULE_PATH, "killSubprocess")
|
.invokePluginFunc(MODULE_PATH, "dispose")
|
||||||
.then((res) => resolve(res));
|
.then((res) => resolve(res));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const inferenceUrl = () => "http://localhost:8080/llama/chat_completion";
|
const inferenceUrl = () => "http://localhost:3928/llama/chat_completion";
|
||||||
|
|
||||||
|
const stopModel = () => {
|
||||||
|
window.electronAPI.invokePluginFunc(MODULE_PATH, "killSubprocess");
|
||||||
|
};
|
||||||
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
// Register all the above functions and objects with the relevant extension points
|
||||||
export function init({ register }) {
|
export function init({ register }) {
|
||||||
register("initModel", "initModel", initModel);
|
register("initModel", "initModel", initModel);
|
||||||
register("inferenceUrl", "inferenceUrl", inferenceUrl);
|
register("inferenceUrl", "inferenceUrl", inferenceUrl);
|
||||||
register("dispose", "dispose", dispose);
|
register("dispose", "dispose", dispose);
|
||||||
|
register("stopModel", "stopModel", stopModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,14 +5,6 @@ const fs = require("fs");
|
|||||||
|
|
||||||
let subprocess = null;
|
let subprocess = null;
|
||||||
|
|
||||||
process.on("exit", () => {
|
|
||||||
// Perform cleanup tasks here
|
|
||||||
console.log("kill subprocess on exit");
|
|
||||||
if (subprocess) {
|
|
||||||
subprocess.kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function initModel(product) {
|
async function initModel(product) {
|
||||||
// fileName fallback
|
// fileName fallback
|
||||||
if (!product.fileName) {
|
if (!product.fileName) {
|
||||||
@ -31,13 +23,13 @@ async function initModel(product) {
|
|||||||
console.error(
|
console.error(
|
||||||
"A subprocess is already running. Attempt to kill then reinit."
|
"A subprocess is already running. Attempt to kill then reinit."
|
||||||
);
|
);
|
||||||
killSubprocess();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
let binaryFolder = `${__dirname}/nitro`; // Current directory by default
|
let binaryFolder = path.join(__dirname, "nitro"); // Current directory by default
|
||||||
|
|
||||||
// Read the existing config
|
// Read the existing config
|
||||||
const configFilePath = `${binaryFolder}/config/config.json`;
|
const configFilePath = path.join(binaryFolder, "config", "config.json");
|
||||||
let config = {};
|
let config = {};
|
||||||
if (fs.existsSync(configFilePath)) {
|
if (fs.existsSync(configFilePath)) {
|
||||||
const rawData = fs.readFileSync(configFilePath, "utf-8");
|
const rawData = fs.readFileSync(configFilePath, "utf-8");
|
||||||
@ -56,8 +48,22 @@ async function initModel(product) {
|
|||||||
// Write the updated config back to the file
|
// Write the updated config back to the file
|
||||||
fs.writeFileSync(configFilePath, JSON.stringify(config, null, 4));
|
fs.writeFileSync(configFilePath, JSON.stringify(config, null, 4));
|
||||||
|
|
||||||
|
let binaryName;
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
binaryName = "nitro_windows_amd64.exe";
|
||||||
|
} else if (process.platform === "darwin") { // Mac OS platform
|
||||||
|
binaryName = process.arch === "arm64" ? "nitro_mac_arm64" : "nitro_mac_amd64";
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
binaryName = "nitro_linux_amd64_cuda"; // For other platforms
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryPath = path.join(binaryFolder, binaryName);
|
||||||
|
|
||||||
// Execute the binary
|
// Execute the binary
|
||||||
subprocess = spawn(`${binaryFolder}/nitro`, [configFilePath]);
|
|
||||||
|
subprocess = spawn(binaryPath, [configFilePath], { cwd: binaryFolder });
|
||||||
|
|
||||||
// Handle subprocess output
|
// Handle subprocess output
|
||||||
subprocess.stdout.on("data", (data) => {
|
subprocess.stdout.on("data", (data) => {
|
||||||
@ -74,6 +80,11 @@ async function initModel(product) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dispose() {
|
||||||
|
killSubprocess();
|
||||||
|
// clean other registered resources here
|
||||||
|
}
|
||||||
|
|
||||||
function killSubprocess() {
|
function killSubprocess() {
|
||||||
if (subprocess) {
|
if (subprocess) {
|
||||||
subprocess.kill();
|
subprocess.kill();
|
||||||
@ -87,4 +98,5 @@ function killSubprocess() {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
initModel,
|
initModel,
|
||||||
killSubprocess,
|
killSubprocess,
|
||||||
|
dispose,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1 +1,13 @@
|
|||||||
{"custom_config": {"llama_model_path":"","ctx_len":2048,"ngl":100}}
|
{
|
||||||
|
"listeners": [
|
||||||
|
{
|
||||||
|
"address": "0.0.0.0",
|
||||||
|
"port": 3928
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"custom_config": {
|
||||||
|
"llama_model_path": "",
|
||||||
|
"ctx_len": 2048,
|
||||||
|
"ngl": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -24,12 +24,59 @@ typedef struct {
|
|||||||
int8_t qs[QK8_0]; // quants
|
int8_t qs[QK8_0]; // quants
|
||||||
} block_q8_0;
|
} block_q8_0;
|
||||||
|
|
||||||
|
// general-purpose kernel for addition of two tensors
|
||||||
|
// pros: works for non-contiguous tensors, supports broadcast across dims 1, 2 and 3
|
||||||
|
// cons: not very efficient
|
||||||
kernel void kernel_add(
|
kernel void kernel_add(
|
||||||
device const float4 * src0,
|
device const char * src0,
|
||||||
device const float4 * src1,
|
device const char * src1,
|
||||||
device float4 * dst,
|
device char * dst,
|
||||||
uint tpig[[thread_position_in_grid]]) {
|
constant int64_t & ne00,
|
||||||
dst[tpig] = src0[tpig] + src1[tpig];
|
constant int64_t & ne01,
|
||||||
|
constant int64_t & ne02,
|
||||||
|
constant int64_t & ne03,
|
||||||
|
constant int64_t & nb00,
|
||||||
|
constant int64_t & nb01,
|
||||||
|
constant int64_t & nb02,
|
||||||
|
constant int64_t & nb03,
|
||||||
|
constant int64_t & ne10,
|
||||||
|
constant int64_t & ne11,
|
||||||
|
constant int64_t & ne12,
|
||||||
|
constant int64_t & ne13,
|
||||||
|
constant int64_t & nb10,
|
||||||
|
constant int64_t & nb11,
|
||||||
|
constant int64_t & nb12,
|
||||||
|
constant int64_t & nb13,
|
||||||
|
constant int64_t & ne0,
|
||||||
|
constant int64_t & ne1,
|
||||||
|
constant int64_t & ne2,
|
||||||
|
constant int64_t & ne3,
|
||||||
|
constant int64_t & nb0,
|
||||||
|
constant int64_t & nb1,
|
||||||
|
constant int64_t & nb2,
|
||||||
|
constant int64_t & nb3,
|
||||||
|
uint3 tgpig[[threadgroup_position_in_grid]],
|
||||||
|
uint3 tpitg[[thread_position_in_threadgroup]],
|
||||||
|
uint3 ntg[[threads_per_threadgroup]]) {
|
||||||
|
const int64_t i03 = tgpig.z;
|
||||||
|
const int64_t i02 = tgpig.y;
|
||||||
|
const int64_t i01 = tgpig.x;
|
||||||
|
|
||||||
|
const int64_t i13 = i03 % ne13;
|
||||||
|
const int64_t i12 = i02 % ne12;
|
||||||
|
const int64_t i11 = i01 % ne11;
|
||||||
|
|
||||||
|
device const char * src0_ptr = src0 + i03*nb03 + i02*nb02 + i01*nb01 + tpitg.x*nb00;
|
||||||
|
device const char * src1_ptr = src1 + i13*nb13 + i12*nb12 + i11*nb11 + tpitg.x*nb10;
|
||||||
|
device char * dst_ptr = dst + i03*nb3 + i02*nb2 + i01*nb1 + tpitg.x*nb0;
|
||||||
|
|
||||||
|
for (int i0 = tpitg.x; i0 < ne0; i0 += ntg.x) {
|
||||||
|
((device float *)dst_ptr)[0] = ((device float *)src0_ptr)[0] + ((device float *)src1_ptr)[0];
|
||||||
|
|
||||||
|
src0_ptr += ntg.x*nb00;
|
||||||
|
src1_ptr += ntg.x*nb10;
|
||||||
|
dst_ptr += ntg.x*nb0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// assumption: src1 is a row
|
// assumption: src1 is a row
|
||||||
@ -38,7 +85,7 @@ kernel void kernel_add_row(
|
|||||||
device const float4 * src0,
|
device const float4 * src0,
|
||||||
device const float4 * src1,
|
device const float4 * src1,
|
||||||
device float4 * dst,
|
device float4 * dst,
|
||||||
constant int64_t & nb,
|
constant int64_t & nb [[buffer(27)]],
|
||||||
uint tpig[[thread_position_in_grid]]) {
|
uint tpig[[thread_position_in_grid]]) {
|
||||||
dst[tpig] = src0[tpig] + src1[tpig % nb];
|
dst[tpig] = src0[tpig] + src1[tpig % nb];
|
||||||
}
|
}
|
||||||
@ -784,6 +831,8 @@ kernel void kernel_alibi_f32(
|
|||||||
constant uint64_t & nb2,
|
constant uint64_t & nb2,
|
||||||
constant uint64_t & nb3,
|
constant uint64_t & nb3,
|
||||||
constant float & m0,
|
constant float & m0,
|
||||||
|
constant float & m1,
|
||||||
|
constant int & n_heads_log2_floor,
|
||||||
uint3 tgpig[[threadgroup_position_in_grid]],
|
uint3 tgpig[[threadgroup_position_in_grid]],
|
||||||
uint3 tpitg[[thread_position_in_threadgroup]],
|
uint3 tpitg[[thread_position_in_threadgroup]],
|
||||||
uint3 ntg[[threads_per_threadgroup]]) {
|
uint3 ntg[[threads_per_threadgroup]]) {
|
||||||
@ -799,15 +848,51 @@ kernel void kernel_alibi_f32(
|
|||||||
const int64_t i0 = (n - i3*ne2*ne1*ne0 - i2*ne1*ne0 - i1*ne0);
|
const int64_t i0 = (n - i3*ne2*ne1*ne0 - i2*ne1*ne0 - i1*ne0);
|
||||||
|
|
||||||
device float * dst_data = (device float *) ((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
device float * dst_data = (device float *) ((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
||||||
float m_k = pow(m0, i2 + 1);
|
float m_k;
|
||||||
|
if (i2 < n_heads_log2_floor) {
|
||||||
|
m_k = pow(m0, i2 + 1);
|
||||||
|
} else {
|
||||||
|
m_k = pow(m1, 2 * (i2 - n_heads_log2_floor) + 1);
|
||||||
|
}
|
||||||
for (int64_t i00 = tpitg.x; i00 < ne00; i00 += ntg.x) {
|
for (int64_t i00 = tpitg.x; i00 < ne00; i00 += ntg.x) {
|
||||||
device const float * src = (device float *)((device char *) src0 + i03*nb03 + i02*nb02 + i01*nb01 + i00*nb00);
|
device const float * src = (device float *)((device char *) src0 + i03*nb03 + i02*nb02 + i01*nb01 + i00*nb00);
|
||||||
dst_data[i00] = src[0] + m_k * (i00 - ne00 + 1);
|
dst_data[i00] = src[0] + m_k * (i00 - ne00 + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void (rope_t)(
|
||||||
|
device const void * src0,
|
||||||
|
device const int32_t * src1,
|
||||||
|
device float * dst,
|
||||||
|
constant int64_t & ne00,
|
||||||
|
constant int64_t & ne01,
|
||||||
|
constant int64_t & ne02,
|
||||||
|
constant int64_t & ne03,
|
||||||
|
constant uint64_t & nb00,
|
||||||
|
constant uint64_t & nb01,
|
||||||
|
constant uint64_t & nb02,
|
||||||
|
constant uint64_t & nb03,
|
||||||
|
constant int64_t & ne0,
|
||||||
|
constant int64_t & ne1,
|
||||||
|
constant int64_t & ne2,
|
||||||
|
constant int64_t & ne3,
|
||||||
|
constant uint64_t & nb0,
|
||||||
|
constant uint64_t & nb1,
|
||||||
|
constant uint64_t & nb2,
|
||||||
|
constant uint64_t & nb3,
|
||||||
|
constant int & n_past,
|
||||||
|
constant int & n_dims,
|
||||||
|
constant int & mode,
|
||||||
|
constant float & freq_base,
|
||||||
|
constant float & freq_scale,
|
||||||
|
uint tiitg[[thread_index_in_threadgroup]],
|
||||||
|
uint3 tptg[[threads_per_threadgroup]],
|
||||||
|
uint3 tgpig[[threadgroup_position_in_grid]]);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
kernel void kernel_rope(
|
kernel void kernel_rope(
|
||||||
device const void * src0,
|
device const void * src0,
|
||||||
|
device const int32_t * src1,
|
||||||
device float * dst,
|
device float * dst,
|
||||||
constant int64_t & ne00,
|
constant int64_t & ne00,
|
||||||
constant int64_t & ne01,
|
constant int64_t & ne01,
|
||||||
@ -839,7 +924,9 @@ kernel void kernel_rope(
|
|||||||
|
|
||||||
const bool is_neox = mode & 2;
|
const bool is_neox = mode & 2;
|
||||||
|
|
||||||
const int64_t p = ((mode & 1) == 0 ? n_past + i2 : i2);
|
device const int32_t * pos = src1;
|
||||||
|
|
||||||
|
const int64_t p = pos[i2];
|
||||||
|
|
||||||
const float theta_0 = freq_scale * (float)p;
|
const float theta_0 = freq_scale * (float)p;
|
||||||
const float inv_ndims = -1.f/n_dims;
|
const float inv_ndims = -1.f/n_dims;
|
||||||
@ -851,11 +938,11 @@ kernel void kernel_rope(
|
|||||||
const float cos_theta = cos(theta);
|
const float cos_theta = cos(theta);
|
||||||
const float sin_theta = sin(theta);
|
const float sin_theta = sin(theta);
|
||||||
|
|
||||||
device const float * const src = (device float *)((device char *) src0 + i3*nb03 + i2*nb02 + i1*nb01 + i0*nb00);
|
device const T * const src = (device T *)((device char *) src0 + i3*nb03 + i2*nb02 + i1*nb01 + i0*nb00);
|
||||||
device float * dst_data = (device float *)((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
device T * dst_data = (device T *)((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
||||||
|
|
||||||
const float x0 = src[0];
|
const T x0 = src[0];
|
||||||
const float x1 = src[1];
|
const T x1 = src[1];
|
||||||
|
|
||||||
dst_data[0] = x0*cos_theta - x1*sin_theta;
|
dst_data[0] = x0*cos_theta - x1*sin_theta;
|
||||||
dst_data[1] = x0*sin_theta + x1*cos_theta;
|
dst_data[1] = x0*sin_theta + x1*cos_theta;
|
||||||
@ -870,8 +957,8 @@ kernel void kernel_rope(
|
|||||||
|
|
||||||
const int64_t i0 = ib*n_dims + ic/2;
|
const int64_t i0 = ib*n_dims + ic/2;
|
||||||
|
|
||||||
device const float * const src = (device float *)((device char *) src0 + i3*nb03 + i2*nb02 + i1*nb01 + i0*nb00);
|
device const T * const src = (device T *)((device char *) src0 + i3*nb03 + i2*nb02 + i1*nb01 + i0*nb00);
|
||||||
device float * dst_data = (device float *)((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
device T * dst_data = (device T *)((device char *) dst + i3*nb3 + i2*nb2 + i1*nb1 + i0*nb0);
|
||||||
|
|
||||||
const float x0 = src[0];
|
const float x0 = src[0];
|
||||||
const float x1 = src[n_dims/2];
|
const float x1 = src[n_dims/2];
|
||||||
@ -883,6 +970,9 @@ kernel void kernel_rope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template [[host_name("kernel_rope_f32")]] kernel rope_t kernel_rope<float>;
|
||||||
|
template [[host_name("kernel_rope_f16")]] kernel rope_t kernel_rope<half>;
|
||||||
|
|
||||||
kernel void kernel_cpy_f16_f16(
|
kernel void kernel_cpy_f16_f16(
|
||||||
device const half * src0,
|
device const half * src0,
|
||||||
device half * dst,
|
device half * dst,
|
||||||
@ -1273,8 +1363,8 @@ kernel void kernel_mul_mat_q3_K_f32(
|
|||||||
|
|
||||||
float yl[32];
|
float yl[32];
|
||||||
|
|
||||||
const uint16_t kmask1 = 0x3030;
|
//const uint16_t kmask1 = 0x3030;
|
||||||
const uint16_t kmask2 = 0x0f0f;
|
//const uint16_t kmask2 = 0x0f0f;
|
||||||
|
|
||||||
const int tid = tiisg/4;
|
const int tid = tiisg/4;
|
||||||
const int ix = tiisg%4;
|
const int ix = tiisg%4;
|
||||||
|
|||||||
BIN
electron/core/plugins/inference-plugin/nitro/nitro_linux_amd64_cuda
Executable file
BIN
electron/core/plugins/inference-plugin/nitro/nitro_mac_amd64
Executable file
BIN
electron/core/plugins/inference-plugin/nitro/zlib.dll
Normal file
3674
electron/core/plugins/inference-plugin/package-lock.json
generated
Normal file
@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "inference-plugin",
|
"name": "inference-plugin",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Inference Plugin, powered by @janhq/nitro, bring a high-performance Llama model inference in pure C++.",
|
||||||
|
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/command-line.svg",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"author": "James",
|
"author": "Jan",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"activationPoints": [
|
"activationPoints": [
|
||||||
"init"
|
"init"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"build:package": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\" && rm -rf dist/nitro && cp -r nitro dist/nitro && npm pack",
|
"postinstall": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\" && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"",
|
||||||
"build:publish": "yarn build:package && cpx *.tgz ../../pre-install"
|
"build:publish": "npm pack && cpx *.tgz ../../pre-install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
@ -24,8 +25,7 @@
|
|||||||
"node-llama-cpp"
|
"node-llama-cpp"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0"
|
||||||
"node-llama-cpp": "^2.4.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
|||||||
@ -38,10 +38,20 @@ const deleteModel = async (path) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const searchModels = async (params) =>
|
||||||
|
new Promise(async (resolve) => {
|
||||||
|
if (window.electronAPI) {
|
||||||
|
window.electronAPI
|
||||||
|
.invokePluginFunc(MODULE_PATH, "searchModels", params)
|
||||||
|
.then((res) => resolve(res));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
// Register all the above functions and objects with the relevant extension points
|
||||||
export function init({ register }) {
|
export function init({ register }) {
|
||||||
register("getDownloadedModels", "getDownloadedModels", getDownloadedModels);
|
register("getDownloadedModels", "getDownloadedModels", getDownloadedModels);
|
||||||
register("getAvailableModels", "getAvailableModels", getAvailableModels);
|
register("getAvailableModels", "getAvailableModels", getAvailableModels);
|
||||||
register("downloadModel", "downloadModel", downloadModel);
|
register("downloadModel", "downloadModel", downloadModel);
|
||||||
register("deleteModel", "deleteModel", deleteModel);
|
register("deleteModel", "deleteModel", deleteModel);
|
||||||
|
register("searchModels", "searchModels", searchModels);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { readdirSync, lstatSync } = require("fs");
|
const { readdirSync, lstatSync } = require("fs");
|
||||||
const { app } = require("electron");
|
const { app } = require("electron");
|
||||||
|
const { listModels, listFiles, fileDownloadInfo } = require("@huggingface/hub");
|
||||||
|
|
||||||
|
let modelsIterator = undefined;
|
||||||
|
let currentSearchOwner = undefined;
|
||||||
|
|
||||||
const ALL_MODELS = [
|
const ALL_MODELS = [
|
||||||
{
|
{
|
||||||
@ -87,6 +91,76 @@ function getDownloadedModels() {
|
|||||||
return downloadedModels;
|
return downloadedModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNextModels = async (count) => {
|
||||||
|
const models = [];
|
||||||
|
let hasMore = true;
|
||||||
|
|
||||||
|
while (models.length < count) {
|
||||||
|
const next = await modelsIterator.next();
|
||||||
|
|
||||||
|
// end if we reached the end
|
||||||
|
if (next.done) {
|
||||||
|
hasMore = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = next.value;
|
||||||
|
const files = await listFilesByName(model.name);
|
||||||
|
|
||||||
|
models.push({
|
||||||
|
...model,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
data: models,
|
||||||
|
hasMore,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchModels = async (params) => {
|
||||||
|
if (currentSearchOwner === params.search.owner && modelsIterator != null) {
|
||||||
|
// paginated search
|
||||||
|
console.debug(`Paginated search owner: ${params.search.owner}`);
|
||||||
|
const models = await getNextModels(params.limit);
|
||||||
|
return models;
|
||||||
|
} else {
|
||||||
|
// new search
|
||||||
|
console.debug(`Init new search owner: ${params.search.owner}`);
|
||||||
|
currentSearchOwner = params.search.owner;
|
||||||
|
modelsIterator = listModels({
|
||||||
|
search: params.search,
|
||||||
|
credentials: params.credentials,
|
||||||
|
});
|
||||||
|
|
||||||
|
const models = await getNextModels(params.limit);
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const listFilesByName = async (modelName) => {
|
||||||
|
const repo = { type: "model", name: modelName };
|
||||||
|
const fileDownloadInfoMap = {};
|
||||||
|
for await (const file of listFiles({
|
||||||
|
repo: repo,
|
||||||
|
})) {
|
||||||
|
if (file.type === "file" && file.path.endsWith(".bin")) {
|
||||||
|
const downloadInfo = await fileDownloadInfo({
|
||||||
|
repo: repo,
|
||||||
|
path: file.path,
|
||||||
|
});
|
||||||
|
fileDownloadInfoMap[file.path] = {
|
||||||
|
...file,
|
||||||
|
...downloadInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileDownloadInfoMap;
|
||||||
|
};
|
||||||
|
|
||||||
function getAvailableModels() {
|
function getAvailableModels() {
|
||||||
const downloadedModelIds = getDownloadedModels().map((model) => model.id);
|
const downloadedModelIds = getDownloadedModels().map((model) => model.id);
|
||||||
return ALL_MODELS.filter((model) => {
|
return ALL_MODELS.filter((model) => {
|
||||||
@ -99,4 +173,5 @@ function getAvailableModels() {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
getDownloadedModels,
|
getDownloadedModels,
|
||||||
getAvailableModels,
|
getAvailableModels,
|
||||||
|
searchModels,
|
||||||
};
|
};
|
||||||
|
|||||||
3607
electron/core/plugins/model-management-plugin/package-lock.json
generated
Normal file
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "model-management-plugin",
|
"name": "model-management-plugin",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Model Management Plugin leverages the HuggingFace API for model exploration and seamless downloads",
|
||||||
|
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/queue-list.svg",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"author": "James",
|
"author": "James",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -10,8 +11,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"build:package": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\" && npm pack",
|
"postinstall": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\"",
|
||||||
"build:publish": "yarn build:package && cpx *.tgz ../../pre-install"
|
"build:publish": "npm pack && cpx *.tgz ../../pre-install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
@ -23,5 +24,11 @@
|
|||||||
"dist/*",
|
"dist/*",
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md"
|
"README.md"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@huggingface/hub": "^0.8.5"
|
||||||
|
},
|
||||||
|
"bundledDependencies": [
|
||||||
|
"@huggingface/hub"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1479
electron/core/plugins/monitoring-plugin/package-lock.json
generated
Normal file
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "monitoring-plugin",
|
"name": "monitoring-plugin",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Utilizing systeminformation, it provides essential System and OS information retrieval",
|
||||||
|
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/cpu-chip.svg",
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"author": "Jan",
|
"author": "Jan",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -10,8 +11,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"build:package": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\" && npm pack",
|
"postinstall": "rimraf ./*.tgz && npm run build && cpx \"module.js\" \"dist\"",
|
||||||
"build:publish": "yarn build:package && cpx *.tgz ../../pre-install"
|
"build:publish": "npm pack && cpx *.tgz ../../pre-install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
|||||||
14
electron/entitlements.mac.plist
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
225
electron/main.ts
@ -1,30 +1,41 @@
|
|||||||
import {
|
import { app, BrowserWindow, ipcMain, dialog, shell } from "electron";
|
||||||
app,
|
|
||||||
BrowserWindow,
|
|
||||||
screen as electronScreen,
|
|
||||||
ipcMain,
|
|
||||||
dialog,
|
|
||||||
shell,
|
|
||||||
} from "electron";
|
|
||||||
import { readdirSync } from "fs";
|
import { readdirSync } from "fs";
|
||||||
import { resolve, join, extname } from "path";
|
import { resolve, join, extname } from "path";
|
||||||
import { rmdir, unlink, createWriteStream } from "fs";
|
import { rmdir, unlink, createWriteStream } from "fs";
|
||||||
import isDev = require("electron-is-dev");
|
|
||||||
import { init } from "./core/plugin-manager/pluginMgr";
|
import { init } from "./core/plugin-manager/pluginMgr";
|
||||||
|
import { setupMenu } from "./utils/menu";
|
||||||
|
import { dispose } from "./utils/disposable";
|
||||||
|
|
||||||
|
const request = require("request");
|
||||||
|
const progress = require("request-progress");
|
||||||
const { autoUpdater } = require("electron-updater");
|
const { autoUpdater } = require("electron-updater");
|
||||||
const Store = require("electron-store");
|
const Store = require("electron-store");
|
||||||
// @ts-ignore
|
|
||||||
import request = require("request");
|
|
||||||
// @ts-ignore
|
|
||||||
import progress = require("request-progress");
|
|
||||||
|
|
||||||
|
const requiredModules: Record<string, any> = {};
|
||||||
let mainWindow: BrowserWindow | undefined = undefined;
|
let mainWindow: BrowserWindow | undefined = undefined;
|
||||||
const store = new Store();
|
|
||||||
|
|
||||||
autoUpdater.autoDownload = false;
|
app
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
.whenReady()
|
||||||
|
.then(migratePlugins)
|
||||||
|
.then(setupPlugins)
|
||||||
|
.then(setupMenu)
|
||||||
|
.then(handleIPCs)
|
||||||
|
.then(handleAppUpdates)
|
||||||
|
.then(createMainWindow)
|
||||||
|
.then(() => {
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (!BrowserWindow.getAllWindows().length) {
|
||||||
|
createMainWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const createMainWindow = () => {
|
app.on("window-all-closed", () => {
|
||||||
|
dispose(requiredModules);
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMainWindow() {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
@ -37,29 +48,9 @@ const createMainWindow = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(
|
const startURL = app.isPackaged
|
||||||
"invokePluginFunc",
|
? `file://${join(__dirname, "../renderer/index.html")}`
|
||||||
async (_event, modulePath, method, ...args) => {
|
: "http://localhost:3000";
|
||||||
const module = join(app.getPath("userData"), "plugins", modulePath);
|
|
||||||
return await import(/* webpackIgnore: true */ module)
|
|
||||||
.then((plugin) => {
|
|
||||||
if (typeof plugin[method] === "function") {
|
|
||||||
return plugin[method](...args);
|
|
||||||
} else {
|
|
||||||
console.log(plugin[method]);
|
|
||||||
console.error(`Function "${method}" does not exist in the module.`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const startURL = isDev
|
|
||||||
? "http://localhost:3000"
|
|
||||||
: `file://${join(__dirname, "../renderer/index.html")}`;
|
|
||||||
|
|
||||||
mainWindow.loadURL(startURL);
|
mainWindow.loadURL(startURL);
|
||||||
|
|
||||||
@ -68,37 +59,107 @@ const createMainWindow = () => {
|
|||||||
if (process.platform !== "darwin") app.quit();
|
if (process.platform !== "darwin") app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDev) mainWindow.webContents.openDevTools();
|
if (!app.isPackaged) mainWindow.webContents.openDevTools();
|
||||||
};
|
}
|
||||||
|
|
||||||
app
|
function handleAppUpdates() {
|
||||||
.whenReady()
|
/*New Update Available*/
|
||||||
.then(migratePlugins)
|
autoUpdater.on("update-available", async (_info: any) => {
|
||||||
.then(() => {
|
const action = await dialog.showMessageBox({
|
||||||
createMainWindow();
|
message: `Update available. Do you want to download the latest update?`,
|
||||||
setupPlugins();
|
buttons: ["Download", "Later"],
|
||||||
|
});
|
||||||
|
if (action.response === 0) await autoUpdater.downloadUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*App Update Completion Message*/
|
||||||
|
autoUpdater.on("update-downloaded", async (_info: any) => {
|
||||||
|
mainWindow?.webContents.send("APP_UPDATE_COMPLETE", {});
|
||||||
|
const action = await dialog.showMessageBox({
|
||||||
|
message: `Update downloaded. Please restart the application to apply the updates.`,
|
||||||
|
buttons: ["Restart", "Later"],
|
||||||
|
});
|
||||||
|
if (action.response === 0) {
|
||||||
|
autoUpdater.quitAndInstall();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*App Update Error */
|
||||||
|
autoUpdater.on("error", (info: any) => {
|
||||||
|
dialog.showMessageBox({ message: info.message });
|
||||||
|
mainWindow?.webContents.send("APP_UPDATE_ERROR", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*App Update Progress */
|
||||||
|
autoUpdater.on("download-progress", (progress: any) => {
|
||||||
|
console.log("app update progress: ", progress.percent);
|
||||||
|
mainWindow?.webContents.send("APP_UPDATE_PROGRESS", {
|
||||||
|
percent: progress.percent,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
autoUpdater.checkForUpdates();
|
autoUpdater.checkForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.handle("basePlugins", async (event) => {
|
function handleIPCs() {
|
||||||
|
ipcMain.handle(
|
||||||
|
"invokePluginFunc",
|
||||||
|
async (_event, modulePath, method, ...args) => {
|
||||||
|
const module = require(/* webpackIgnore: true */ join(
|
||||||
|
app.getPath("userData"),
|
||||||
|
"plugins",
|
||||||
|
modulePath
|
||||||
|
));
|
||||||
|
requiredModules[modulePath] = module;
|
||||||
|
|
||||||
|
if (typeof module[method] === "function") {
|
||||||
|
return module[method](...args);
|
||||||
|
} else {
|
||||||
|
console.log(module[method]);
|
||||||
|
console.error(`Function "${method}" does not exist in the module.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ipcMain.handle("basePlugins", async (_event) => {
|
||||||
const basePluginPath = join(
|
const basePluginPath = join(
|
||||||
__dirname,
|
__dirname,
|
||||||
"../",
|
"../",
|
||||||
isDev ? "/core/pre-install" : "../app.asar.unpacked/core/pre-install"
|
app.isPackaged
|
||||||
|
? "../app.asar.unpacked/core/pre-install"
|
||||||
|
: "/core/pre-install"
|
||||||
);
|
);
|
||||||
return readdirSync(basePluginPath)
|
return readdirSync(basePluginPath)
|
||||||
.filter((file) => extname(file) === ".tgz")
|
.filter((file) => extname(file) === ".tgz")
|
||||||
.map((file) => join(basePluginPath, file));
|
.map((file) => join(basePluginPath, file));
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("pluginPath", async (event) => {
|
ipcMain.handle("pluginPath", async (_event) => {
|
||||||
return join(app.getPath("userData"), "plugins");
|
return join(app.getPath("userData"), "plugins");
|
||||||
});
|
});
|
||||||
ipcMain.handle("appVersion", async (event) => {
|
ipcMain.handle("appVersion", async (_event) => {
|
||||||
return app.getVersion();
|
return app.getVersion();
|
||||||
});
|
});
|
||||||
ipcMain.handle("openExternalUrl", async (event, url) => {
|
ipcMain.handle("openExternalUrl", async (_event, url) => {
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
});
|
});
|
||||||
|
ipcMain.handle("relaunch", async (_event, url) => {
|
||||||
|
dispose(requiredModules);
|
||||||
|
app.relaunch();
|
||||||
|
app.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("reloadPlugins", async (_event, url) => {
|
||||||
|
const userDataPath = app.getPath("userData");
|
||||||
|
const fullPath = join(userDataPath, "plugins");
|
||||||
|
|
||||||
|
rmdir(fullPath, { recursive: true }, function (err) {
|
||||||
|
if (err) console.log(err);
|
||||||
|
app.relaunch();
|
||||||
|
app.exit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to delete a file from the user data folder
|
* Used to delete a file from the user data folder
|
||||||
@ -116,9 +177,7 @@ app
|
|||||||
} else {
|
} else {
|
||||||
result = "File deleted successfully";
|
result = "File deleted successfully";
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(`Delete file ${filePath} from ${fullPath} result: ${result}`);
|
||||||
`Delete file ${filePath} from ${fullPath} result: ${result}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -151,57 +210,11 @@ app
|
|||||||
})
|
})
|
||||||
.pipe(createWriteStream(destination));
|
.pipe(createWriteStream(destination));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
app.on("activate", () => {
|
|
||||||
if (!BrowserWindow.getAllWindows().length) {
|
|
||||||
createMainWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/*New Update Available*/
|
|
||||||
autoUpdater.on("update-available", async (info: any) => {
|
|
||||||
const action = await dialog.showMessageBox({
|
|
||||||
message: `Update available. Do you want to download the latest update?`,
|
|
||||||
buttons: ["Download", "Later"],
|
|
||||||
});
|
|
||||||
if (action.response === 0) await autoUpdater.downloadUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
/*App Update Completion Message*/
|
|
||||||
autoUpdater.on("update-downloaded", async (info: any) => {
|
|
||||||
mainWindow?.webContents.send("APP_UPDATE_COMPLETE", {});
|
|
||||||
const action = await dialog.showMessageBox({
|
|
||||||
message: `Update downloaded. Please restart the application to apply the updates.`,
|
|
||||||
buttons: ["Restart", "Later"],
|
|
||||||
});
|
|
||||||
if (action.response === 0) {
|
|
||||||
autoUpdater.quitAndInstall();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*App Update Error */
|
|
||||||
autoUpdater.on("error", (info: any) => {
|
|
||||||
dialog.showMessageBox({ message: info.message });
|
|
||||||
mainWindow?.webContents.send("APP_UPDATE_ERROR", {});
|
|
||||||
});
|
|
||||||
|
|
||||||
/*App Update Progress */
|
|
||||||
autoUpdater.on("download-progress", (progress: any) => {
|
|
||||||
console.log("app update progress: ", progress.percent);
|
|
||||||
mainWindow?.webContents.send("APP_UPDATE_PROGRESS", {
|
|
||||||
percent: progress.percent,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on("window-all-closed", () => {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function migratePlugins() {
|
function migratePlugins() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
const store = new Store();
|
||||||
if (store.get("migrated_version") !== app.getVersion()) {
|
if (store.get("migrated_version") !== app.getVersion()) {
|
||||||
console.log("start migration:", store.get("migrated_version"));
|
console.log("start migration:", store.get("migrated_version"));
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
@ -217,12 +230,12 @@ function migratePlugins() {
|
|||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
function setupPlugins() {
|
function setupPlugins() {
|
||||||
init({
|
init({
|
||||||
// Function to check from the main process that user wants to install a plugin
|
// Function to check from the main process that user wants to install a plugin
|
||||||
confirmInstall: async (plugins: string[]) => {
|
confirmInstall: async (_plugins: string[]) => {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// Path to install plugin to
|
// Path to install plugin to
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "jan-electron",
|
"name": "jan-electron",
|
||||||
"version": "0.1.1",
|
"version": "0.1.3",
|
||||||
"main": "./build/main.js",
|
"main": "./build/main.js",
|
||||||
"author": "Jan",
|
"author": "Jan <service@jan.ai>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
"build": {
|
"build": {
|
||||||
@ -11,8 +11,9 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"renderer/**/*",
|
"renderer/**/*",
|
||||||
"build/*.{js,map}",
|
"build/*.{js,map}",
|
||||||
"build/core/plugin-manager/**/*",
|
"build/**/*.{js,map}",
|
||||||
"core/pre-install"
|
"core/pre-install",
|
||||||
|
"core/plugin-manager/facade"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"core/pre-install"
|
"core/pre-install"
|
||||||
@ -26,29 +27,48 @@
|
|||||||
],
|
],
|
||||||
"extends": null,
|
"extends": null,
|
||||||
"mac": {
|
"mac": {
|
||||||
"type": "distribution"
|
"type": "distribution",
|
||||||
|
"entitlements": "./entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "./entitlements.mac.plist",
|
||||||
|
"notarize": {
|
||||||
|
"teamId": "YT49P7GXG4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"artifactName": "jan-${os}-${arch}-${version}.${ext}"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"",
|
||||||
|
"test:e2e": "playwright test --workers=1",
|
||||||
"dev": "tsc -p . && electron .",
|
"dev": "tsc -p . && electron .",
|
||||||
"build": "tsc -p . && electron-builder -p never -mw",
|
"build": "tsc -p . && electron-builder -p never -m",
|
||||||
"build:publish": "tsc -p . && electron-builder -p onTagOrDraft -mw",
|
"build:darwin": "tsc -p . && electron-builder -p never -m --x64 --arm64",
|
||||||
"postinstall": "electron-builder install-app-deps"
|
"build:win32": "tsc -p . && electron-builder -p never -w",
|
||||||
|
"build:linux": "tsc -p . && electron-builder -p never --linux deb",
|
||||||
|
"build:publish": "tsc -p . && electron-builder -p onTagOrDraft -m",
|
||||||
|
"build:publish-darwin": "tsc -p . && electron-builder -p onTagOrDraft -m --x64 --arm64",
|
||||||
|
"build:publish-win32": "tsc -p . && electron-builder -p onTagOrDraft -w",
|
||||||
|
"build:publish-linux": "tsc -p . && electron-builder -p onTagOrDraft --linux deb "
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-is-dev": "^2.0.0",
|
"@npmcli/arborist": "^7.1.0",
|
||||||
|
"@uiball/loaders": "^1.3.0",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "^6.1.4",
|
"electron-updater": "^6.1.4",
|
||||||
"node-llama-cpp": "^2.4.1",
|
"pacote": "^17.0.4",
|
||||||
"pluggable-electron": "^0.6.0",
|
"react-intersection-observer": "^9.5.2",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-progress": "^3.0.0"
|
"request-progress": "^3.0.0",
|
||||||
|
"use-debounce": "^9.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"@electron/notarize": "^2.1.0",
|
||||||
|
"@playwright/test": "^1.38.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"electron": "26.2.1",
|
"electron": "26.2.1",
|
||||||
"electron-builder": "^24.6.4",
|
"electron-builder": "^24.6.4",
|
||||||
"wait-on": "^7.0.1"
|
"electron-playwright-helpers": "^1.6.0",
|
||||||
|
"eslint-plugin-react": "^7.33.2"
|
||||||
},
|
},
|
||||||
"installConfig": {
|
"installConfig": {
|
||||||
"hoistingLimits": "workspaces"
|
"hoistingLimits": "workspaces"
|
||||||
|
|||||||
10
electron/playwright.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { PlaywrightTestConfig } from "@playwright/test";
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
testDir: "./tests",
|
||||||
|
testIgnore: "./core/**",
|
||||||
|
retries: 0,
|
||||||
|
timeout: 120000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
|
||||||
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const useFacade = require("pluggable-electron/facade");
|
const useFacade = require("../core/plugin-manager/facade");
|
||||||
useFacade();
|
useFacade();
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const { contextBridge, ipcRenderer } = require("electron");
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
@ -14,10 +13,14 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
|||||||
|
|
||||||
pluginPath: () => ipcRenderer.invoke("pluginPath"),
|
pluginPath: () => ipcRenderer.invoke("pluginPath"),
|
||||||
|
|
||||||
|
reloadPlugins: () => ipcRenderer.invoke("reloadPlugins"),
|
||||||
|
|
||||||
appVersion: () => ipcRenderer.invoke("appVersion"),
|
appVersion: () => ipcRenderer.invoke("appVersion"),
|
||||||
|
|
||||||
openExternalUrl: (url: string) => ipcRenderer.invoke("openExternalUrl", url),
|
openExternalUrl: (url: string) => ipcRenderer.invoke("openExternalUrl", url),
|
||||||
|
|
||||||
|
relaunch: () => ipcRenderer.invoke("relaunch"),
|
||||||
|
|
||||||
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
|
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
|
||||||
|
|
||||||
downloadFile: (url: string, path: string) =>
|
downloadFile: (url: string, path: string) =>
|
||||||
|
|||||||
47
electron/tests/explore.e2e.spec.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import { ElectronApplication, Page, expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
findLatestBuild,
|
||||||
|
parseElectronApp,
|
||||||
|
stubDialog,
|
||||||
|
} from "electron-playwright-helpers";
|
||||||
|
|
||||||
|
let electronApp: ElectronApplication;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
process.env.CI = "e2e";
|
||||||
|
|
||||||
|
const latestBuild = findLatestBuild("dist");
|
||||||
|
expect(latestBuild).toBeTruthy();
|
||||||
|
|
||||||
|
// parse the packaged Electron app and find paths and other info
|
||||||
|
const appInfo = parseElectronApp(latestBuild);
|
||||||
|
expect(appInfo).toBeTruthy();
|
||||||
|
|
||||||
|
electronApp = await electron.launch({
|
||||||
|
args: [appInfo.main], // main file from package.json
|
||||||
|
executablePath: appInfo.executable, // path to the Electron executable
|
||||||
|
});
|
||||||
|
await stubDialog(electronApp, "showMessageBox", { response: 1 });
|
||||||
|
|
||||||
|
page = await electronApp.firstWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await electronApp.close();
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("explores models", async () => {
|
||||||
|
await page.getByRole("button", { name: "Explore Models" }).first().click();
|
||||||
|
const header = await page
|
||||||
|
.getByRole("heading")
|
||||||
|
.filter({ hasText: "Explore Models" })
|
||||||
|
.first()
|
||||||
|
.isDisabled();
|
||||||
|
expect(header).toBe(false);
|
||||||
|
|
||||||
|
// More test cases here...
|
||||||
|
});
|
||||||
57
electron/tests/main.e2e.spec.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import { ElectronApplication, Page, expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
findLatestBuild,
|
||||||
|
parseElectronApp,
|
||||||
|
stubDialog,
|
||||||
|
} from "electron-playwright-helpers";
|
||||||
|
|
||||||
|
let electronApp: ElectronApplication;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
process.env.CI = "e2e";
|
||||||
|
|
||||||
|
const latestBuild = findLatestBuild("dist");
|
||||||
|
expect(latestBuild).toBeTruthy();
|
||||||
|
|
||||||
|
// parse the packaged Electron app and find paths and other info
|
||||||
|
const appInfo = parseElectronApp(latestBuild);
|
||||||
|
expect(appInfo).toBeTruthy();
|
||||||
|
expect(appInfo.asar).toBe(true);
|
||||||
|
expect(appInfo.executable).toBeTruthy();
|
||||||
|
expect(appInfo.main).toBeTruthy();
|
||||||
|
expect(appInfo.name).toBe("jan-electron");
|
||||||
|
expect(appInfo.packageJson).toBeTruthy();
|
||||||
|
expect(appInfo.packageJson.name).toBe("jan-electron");
|
||||||
|
expect(appInfo.platform).toBeTruthy();
|
||||||
|
expect(appInfo.platform).toBe(process.platform);
|
||||||
|
expect(appInfo.resourcesDir).toBeTruthy();
|
||||||
|
|
||||||
|
electronApp = await electron.launch({
|
||||||
|
args: [appInfo.main], // main file from package.json
|
||||||
|
executablePath: appInfo.executable, // path to the Electron executable
|
||||||
|
});
|
||||||
|
await stubDialog(electronApp, "showMessageBox", { response: 1 });
|
||||||
|
|
||||||
|
page = await electronApp.firstWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await electronApp.close();
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders the home page", async () => {
|
||||||
|
expect(page).toBeDefined();
|
||||||
|
|
||||||
|
// Welcome text is available
|
||||||
|
const welcomeText = await page
|
||||||
|
.locator(".text-5xl", {
|
||||||
|
hasText: "Welcome,let’s download your first model",
|
||||||
|
})
|
||||||
|
.first()
|
||||||
|
.isDisabled();
|
||||||
|
expect(welcomeText).toBe(false);
|
||||||
|
});
|
||||||
46
electron/tests/my-models.e2e.spec.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import { ElectronApplication, Page, expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
findLatestBuild,
|
||||||
|
parseElectronApp,
|
||||||
|
stubDialog,
|
||||||
|
} from "electron-playwright-helpers";
|
||||||
|
|
||||||
|
let electronApp: ElectronApplication;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
process.env.CI = "e2e";
|
||||||
|
|
||||||
|
const latestBuild = findLatestBuild("dist");
|
||||||
|
expect(latestBuild).toBeTruthy();
|
||||||
|
|
||||||
|
// parse the packaged Electron app and find paths and other info
|
||||||
|
const appInfo = parseElectronApp(latestBuild);
|
||||||
|
expect(appInfo).toBeTruthy();
|
||||||
|
|
||||||
|
electronApp = await electron.launch({
|
||||||
|
args: [appInfo.main], // main file from package.json
|
||||||
|
executablePath: appInfo.executable, // path to the Electron executable
|
||||||
|
});
|
||||||
|
await stubDialog(electronApp, "showMessageBox", { response: 1 });
|
||||||
|
|
||||||
|
page = await electronApp.firstWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await electronApp.close();
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows my models", async () => {
|
||||||
|
await page.getByRole("button", { name: "My Models" }).first().click();
|
||||||
|
const header = await page
|
||||||
|
.getByRole("heading")
|
||||||
|
.filter({ hasText: "My Models" })
|
||||||
|
.first()
|
||||||
|
.isDisabled();
|
||||||
|
expect(header).toBe(false);
|
||||||
|
// More test cases here...
|
||||||
|
});
|
||||||
76
electron/tests/navigation.e2e.spec.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import { ElectronApplication, Page, expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
findLatestBuild,
|
||||||
|
parseElectronApp,
|
||||||
|
stubDialog,
|
||||||
|
} from "electron-playwright-helpers";
|
||||||
|
|
||||||
|
let electronApp: ElectronApplication;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
process.env.CI = "e2e";
|
||||||
|
|
||||||
|
const latestBuild = findLatestBuild("dist");
|
||||||
|
expect(latestBuild).toBeTruthy();
|
||||||
|
|
||||||
|
// parse the packaged Electron app and find paths and other info
|
||||||
|
const appInfo = parseElectronApp(latestBuild);
|
||||||
|
expect(appInfo).toBeTruthy();
|
||||||
|
|
||||||
|
electronApp = await electron.launch({
|
||||||
|
args: [appInfo.main], // main file from package.json
|
||||||
|
executablePath: appInfo.executable, // path to the Electron executable
|
||||||
|
});
|
||||||
|
await stubDialog(electronApp, "showMessageBox", { response: 1 });
|
||||||
|
|
||||||
|
page = await electronApp.firstWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await electronApp.close();
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders left navigation panel", async () => {
|
||||||
|
// Chat History section is available
|
||||||
|
const chatSection = await page
|
||||||
|
.getByRole("heading")
|
||||||
|
.filter({ hasText: "CHAT HISTORY" })
|
||||||
|
.first()
|
||||||
|
.isDisabled();
|
||||||
|
expect(chatSection).toBe(false);
|
||||||
|
|
||||||
|
// Home actions
|
||||||
|
const newChatBtn = await page
|
||||||
|
.getByRole("button", { name: "New Chat" })
|
||||||
|
.first()
|
||||||
|
.isEnabled();
|
||||||
|
const exploreBtn = await page
|
||||||
|
.getByRole("button", { name: "Explore Models" })
|
||||||
|
.first()
|
||||||
|
.isEnabled();
|
||||||
|
const discordBtn = await page
|
||||||
|
.getByRole("button", { name: "Discord" })
|
||||||
|
.first()
|
||||||
|
.isEnabled();
|
||||||
|
const myModelsBtn = await page
|
||||||
|
.getByRole("button", { name: "My Models" })
|
||||||
|
.first()
|
||||||
|
.isEnabled();
|
||||||
|
const settingsBtn = await page
|
||||||
|
.getByRole("button", { name: "Settings" })
|
||||||
|
.first()
|
||||||
|
.isEnabled();
|
||||||
|
expect(
|
||||||
|
[
|
||||||
|
newChatBtn,
|
||||||
|
exploreBtn,
|
||||||
|
discordBtn,
|
||||||
|
myModelsBtn,
|
||||||
|
settingsBtn,
|
||||||
|
].filter((e) => !e).length
|
||||||
|
).toBe(0);
|
||||||
|
});
|
||||||
42
electron/tests/settings.e2e.spec.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { _electron as electron } from "playwright";
|
||||||
|
import { ElectronApplication, Page, expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
findLatestBuild,
|
||||||
|
parseElectronApp,
|
||||||
|
stubDialog,
|
||||||
|
} from "electron-playwright-helpers";
|
||||||
|
|
||||||
|
let electronApp: ElectronApplication;
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async () => {
|
||||||
|
process.env.CI = "e2e";
|
||||||
|
|
||||||
|
const latestBuild = findLatestBuild("dist");
|
||||||
|
expect(latestBuild).toBeTruthy();
|
||||||
|
|
||||||
|
// parse the packaged Electron app and find paths and other info
|
||||||
|
const appInfo = parseElectronApp(latestBuild);
|
||||||
|
expect(appInfo).toBeTruthy();
|
||||||
|
|
||||||
|
electronApp = await electron.launch({
|
||||||
|
args: [appInfo.main], // main file from package.json
|
||||||
|
executablePath: appInfo.executable, // path to the Electron executable
|
||||||
|
});
|
||||||
|
await stubDialog(electronApp, "showMessageBox", { response: 1 });
|
||||||
|
|
||||||
|
page = await electronApp.firstWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterAll(async () => {
|
||||||
|
await electronApp.close();
|
||||||
|
await page.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows settings", async () => {
|
||||||
|
await page.getByRole("button", { name: "Settings" }).first().click();
|
||||||
|
|
||||||
|
const pluginList = await page.getByTestId("plugin-item").count();
|
||||||
|
expect(pluginList).toBe(4);
|
||||||
|
});
|
||||||
@ -2,13 +2,17 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"noImplicitAny": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true,
|
||||||
|
"baseUrl": ".",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"paths": { "*": ["node_modules/*"] },
|
||||||
"typeRoots": ["node_modules/@types"]
|
"typeRoots": ["node_modules/@types"]
|
||||||
},
|
},
|
||||||
"exclude": ["core", "build", "node_modules"]
|
"include": ["./**/*.ts"],
|
||||||
|
"exclude": ["core", "build", "dist", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
8
electron/utils/disposable.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export function dispose(requiredModules: Record<string, any>) {
|
||||||
|
for (const key in requiredModules) {
|
||||||
|
const module = requiredModules[key];
|
||||||
|
if (typeof module["dispose"] === "function") {
|
||||||
|
module["dispose"]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
electron/utils/menu.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
const { app, Menu, dialog } = require("electron");
|
||||||
|
const isMac = process.platform === "darwin";
|
||||||
|
const { autoUpdater } = require("electron-updater");
|
||||||
|
import { compareSemanticVersions } from "./versionDiff";
|
||||||
|
|
||||||
|
const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
||||||
|
...(isMac
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: app.name,
|
||||||
|
submenu: [
|
||||||
|
{ role: "about" },
|
||||||
|
{
|
||||||
|
label: "Check for Updates...",
|
||||||
|
click: () =>
|
||||||
|
autoUpdater.checkForUpdatesAndNotify().then((e) => {
|
||||||
|
if (
|
||||||
|
!e ||
|
||||||
|
compareSemanticVersions(
|
||||||
|
app.getVersion(),
|
||||||
|
e.updateInfo.version
|
||||||
|
) >= 0
|
||||||
|
)
|
||||||
|
dialog.showMessageBox({
|
||||||
|
message: `There are currently no updates available.`,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "services" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "hide" },
|
||||||
|
{ role: "hideOthers" },
|
||||||
|
{ role: "unhide" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "quit" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
label: "Edit",
|
||||||
|
submenu: [
|
||||||
|
{ role: "undo" },
|
||||||
|
{ role: "redo" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "cut" },
|
||||||
|
{ role: "copy" },
|
||||||
|
{ role: "paste" },
|
||||||
|
...(isMac
|
||||||
|
? [
|
||||||
|
{ role: "pasteAndMatchStyle" },
|
||||||
|
{ role: "delete" },
|
||||||
|
{ role: "selectAll" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{
|
||||||
|
label: "Speech",
|
||||||
|
submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "View",
|
||||||
|
submenu: [
|
||||||
|
{ role: "reload" },
|
||||||
|
{ role: "forceReload" },
|
||||||
|
{ role: "toggleDevTools" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "resetZoom" },
|
||||||
|
{ role: "zoomIn" },
|
||||||
|
{ role: "zoomOut" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "togglefullscreen" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Window",
|
||||||
|
submenu: [
|
||||||
|
{ role: "minimize" },
|
||||||
|
{ role: "zoom" },
|
||||||
|
...(isMac
|
||||||
|
? [
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "front" },
|
||||||
|
{ type: "separator" },
|
||||||
|
{ role: "window" },
|
||||||
|
]
|
||||||
|
: [{ role: "close" }]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "help",
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: "Learn More",
|
||||||
|
click: async () => {
|
||||||
|
const { shell } = require("electron");
|
||||||
|
await shell.openExternal("https://jan.ai/");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const setupMenu = () => {
|
||||||
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
};
|
||||||
21
electron/utils/versionDiff.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const compareSemanticVersions = (a: string, b: string) => {
|
||||||
|
|
||||||
|
// 1. Split the strings into their parts.
|
||||||
|
const a1 = a.split('.');
|
||||||
|
const b1 = b.split('.');
|
||||||
|
// 2. Contingency in case there's a 4th or 5th version
|
||||||
|
const len = Math.min(a1.length, b1.length);
|
||||||
|
// 3. Look through each version number and compare.
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const a2 = +a1[ i ] || 0;
|
||||||
|
const b2 = +b1[ i ] || 0;
|
||||||
|
|
||||||
|
if (a2 !== b2) {
|
||||||
|
return a2 > b2 ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. We hit this if the all checked versions so far are equal
|
||||||
|
//
|
||||||
|
return b1.length - a1.length;
|
||||||
|
};
|
||||||
1614
node_modules/.yarn-integrity
generated
vendored
14224
package-lock.json
generated
Normal file
18
package.json
@ -14,19 +14,29 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"lint": "yarn workspace jan-electron lint && yarn workspace jan-web lint",
|
||||||
|
"test": "yarn workspace jan-electron test:e2e",
|
||||||
"dev:electron": "yarn workspace jan-electron dev",
|
"dev:electron": "yarn workspace jan-electron dev",
|
||||||
"dev:web": "yarn workspace jan-web dev",
|
"dev:web": "yarn workspace jan-web dev",
|
||||||
"dev": "concurrently --kill-others-on-fail \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
||||||
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
||||||
"build:electron": "yarn workspace jan-electron build",
|
"build:electron": "yarn workspace jan-electron build",
|
||||||
"build:plugins": "rm -f ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm install && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm install && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm install && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm install && npm run build:publish\"",
|
"build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm ci\" \"cd ./electron/core/plugins/inference-plugin && npm ci\" \"cd ./electron/core/plugins/model-management-plugin && npm ci\" \"cd ./electron/core/plugins/monitoring-plugin && npm ci\" && concurrently \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
|
||||||
|
"build:plugins-darwin": "rimraf ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm ci\" \"cd ./electron/core/plugins/inference-plugin && npm ci\" \"cd ./electron/core/plugins/model-management-plugin && npm ci\" \"cd ./electron/core/plugins/monitoring-plugin && npm ci\" && chmod +x ./electron/auto-sign.sh && ./electron/auto-sign.sh && concurrently \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
|
||||||
"build": "yarn build:web && yarn build:electron",
|
"build": "yarn build:web && yarn build:electron",
|
||||||
"build:publish": "yarn build:web && yarn workspace jan-electron build:publish"
|
"build:darwin": "yarn build:web && yarn workspace jan-electron build:darwin",
|
||||||
|
"build:win32": "yarn build:web && yarn workspace jan-electron build:win32",
|
||||||
|
"build:linux": "yarn build:web && yarn workspace jan-electron build:linux",
|
||||||
|
"build:publish": "yarn build:web && yarn workspace jan-electron build:publish",
|
||||||
|
"build:publish-darwin": "yarn build:web && yarn workspace jan-electron build:publish-darwin",
|
||||||
|
"build:publish-win32": "yarn build:web && yarn workspace jan-electron build:publish-win32",
|
||||||
|
"build:publish-linux": "yarn build:web && yarn workspace jan-electron build:publish-linux"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"concurrently": "^8.2.1",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"wait-on": "^7.0.1"
|
"wait-on": "^7.0.1",
|
||||||
|
"rimraf": "^3.0.2"
|
||||||
},
|
},
|
||||||
"version": "0.0.0"
|
"version": "0.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
19
web/app/_components/ActiveModelTable/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
import ModelTable from "../ModelTable";
|
||||||
|
import { currentProductAtom } from "@/_helpers/atoms/Model.atom";
|
||||||
|
|
||||||
|
const ActiveModelTable: React.FC = () => {
|
||||||
|
const activeModel = useAtomValue(currentProductAtom);
|
||||||
|
|
||||||
|
if (!activeModel) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pl-[63px] pr-[89px]">
|
||||||
|
<h3 className="text-xl leading-[25px] mb-[13px]">Active Model(s)</h3>
|
||||||
|
<ModelTable models={[activeModel]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActiveModelTable;
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import { MenuAdvancedPrompt } from "../MenuAdvancedPrompt";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import BasicPromptButton from "../BasicPromptButton";
|
|
||||||
import PrimaryButton from "../PrimaryButton";
|
|
||||||
|
|
||||||
const AdvancedPrompt: React.FC = () => {
|
|
||||||
const { register, handleSubmit } = useForm();
|
|
||||||
|
|
||||||
const onSubmit = (data: any) => {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
className="w-[288px] h-screen flex flex-col border-r border-gray-200"
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
>
|
|
||||||
<BasicPromptButton />
|
|
||||||
<MenuAdvancedPrompt register={register} />
|
|
||||||
<div className="py-3 px-2 flex flex-none gap-3 items-center justify-between border-t border-gray-200">
|
|
||||||
<PrimaryButton
|
|
||||||
fullWidth={true}
|
|
||||||
title="Generate"
|
|
||||||
onClick={() => handleSubmit(onSubmit)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedPrompt;
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import TogglableHeader from "../TogglableHeader";
|
|
||||||
|
|
||||||
const AdvancedPromptGenerationParams = () => {
|
|
||||||
const [expand, setExpand] = useState(true);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TogglableHeader
|
|
||||||
icon={"icons/unicorn_layers-alt.svg"}
|
|
||||||
title={"Generation Parameters"}
|
|
||||||
expand={expand}
|
|
||||||
onTitleClick={() => setExpand(!expand)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedPromptGenerationParams;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { DropdownsList } from "../DropdownList";
|
|
||||||
import TogglableHeader from "../TogglableHeader";
|
|
||||||
import { UploadFileImage } from "../UploadFileImage";
|
|
||||||
import { FieldValues, UseFormRegister } from "react-hook-form";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
register: UseFormRegister<FieldValues>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdvancedPromptImageUpload: React.FC<Props> = ({ register }) => {
|
|
||||||
const [expand, setExpand] = useState(true);
|
|
||||||
const data = ["test1", "test2", "test3", "test4"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TogglableHeader
|
|
||||||
icon={"icons/ic_image.svg"}
|
|
||||||
title={"Image"}
|
|
||||||
expand={expand}
|
|
||||||
onTitleClick={() => setExpand(!expand)}
|
|
||||||
/>
|
|
||||||
<div className={`${expand ? "flex" : "hidden"} flex-col gap-[5px]`}>
|
|
||||||
<UploadFileImage register={register} />
|
|
||||||
<DropdownsList title="Control image with ControlNet:" data={data} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedPromptImageUpload;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { DropdownsList } from "../DropdownList";
|
|
||||||
import TogglableHeader from "../TogglableHeader";
|
|
||||||
|
|
||||||
const AdvancedPromptResolution = () => {
|
|
||||||
const [expand, setExpand] = useState(true);
|
|
||||||
const data = ["512", "524", "536"];
|
|
||||||
const ratioData = ["1:1", "2:2", "3:3"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TogglableHeader
|
|
||||||
icon={"icons/unicorn_layers-alt.svg"}
|
|
||||||
title={"Resolution"}
|
|
||||||
expand={expand}
|
|
||||||
onTitleClick={() => setExpand(!expand)}
|
|
||||||
/>
|
|
||||||
<div className={`${expand ? "flex" : "hidden"} flex-col gap-[5px]`}>
|
|
||||||
<div className="flex gap-3 py-3">
|
|
||||||
<DropdownsList data={data} title="Width" />
|
|
||||||
<DropdownsList data={data} title="Height" />
|
|
||||||
</div>
|
|
||||||
<DropdownsList title="Select ratio" data={ratioData} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedPromptResolution;
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import TogglableHeader from "../TogglableHeader";
|
|
||||||
import { AdvancedTextArea } from "../AdvancedTextArea";
|
|
||||||
import { FieldValues, UseFormRegister } from "react-hook-form";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
register: UseFormRegister<FieldValues>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AdvancedPromptText: React.FC<Props> = ({ register }) => {
|
|
||||||
const [expand, setExpand] = useState(true);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TogglableHeader
|
|
||||||
icon={"icons/messicon.svg"}
|
|
||||||
title={"Prompt"}
|
|
||||||
expand={expand}
|
|
||||||
onTitleClick={() => setExpand(!expand)}
|
|
||||||
/>
|
|
||||||
<div className={`${expand ? "flex" : "hidden"} flex-col gap-[5px]`}>
|
|
||||||
<AdvancedTextArea
|
|
||||||
formId="prompt"
|
|
||||||
height={80}
|
|
||||||
placeholder="Prompt"
|
|
||||||
title="Prompt"
|
|
||||||
register={register}
|
|
||||||
/>
|
|
||||||
<AdvancedTextArea
|
|
||||||
formId="negativePrompt"
|
|
||||||
height={80}
|
|
||||||
placeholder="Describe what you don't want in your image"
|
|
||||||
title="Negative Prompt"
|
|
||||||
register={register}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvancedPromptText;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { FieldValues, UseFormRegister } from "react-hook-form";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
formId?: string;
|
|
||||||
height: number;
|
|
||||||
title: string;
|
|
||||||
placeholder: string;
|
|
||||||
register: UseFormRegister<FieldValues>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AdvancedTextArea: React.FC<Props> = ({
|
|
||||||
formId = "",
|
|
||||||
height,
|
|
||||||
placeholder,
|
|
||||||
title,
|
|
||||||
register,
|
|
||||||
}) => (
|
|
||||||
<div className="w-full flex flex-col pt-3 gap-1">
|
|
||||||
<label className="text-sm leading-5 text-gray-800">{title}</label>
|
|
||||||
<textarea
|
|
||||||
style={{ height: `${height}px` }}
|
|
||||||
className="rounded-lg py-[13px] px-5 border outline-none resize-none border-gray-300 bg-gray-50 placeholder:gray-400 text-sm font-normal"
|
|
||||||
placeholder={placeholder}
|
|
||||||
{...register(formId, { required: formId === "prompt" ? true : false })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
const Search: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="flex bg-gray-200 w-[343px] h-[36px] items-center px-2 gap-[6px] rounded-md">
|
|
||||||
<Image
|
|
||||||
src={"icons/magnifyingglass.svg"}
|
|
||||||
width={15.63}
|
|
||||||
height={15.78}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="bg-inherit outline-0 w-full border-0 p-0 focus:ring-0"
|
|
||||||
placeholder="Search"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Search;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
name: string;
|
|
||||||
imageUrl: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AiTypeCard: React.FC<Props> = ({ imageUrl, name }) => {
|
|
||||||
return (
|
|
||||||
<Link href={`/ai/${name}`} className='flex-1'>
|
|
||||||
<div className="flex-1 h-full bg-[#F3F4F6] flex items-center justify-center gap-[10px] py-[13px] rounded-[8px] px-4 active:opacity-50 hover:opacity-20">
|
|
||||||
<Image src={imageUrl} width={82} height={82} alt="" />
|
|
||||||
<span className="font-bold">{name}</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AiTypeCard;
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
type Props = {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ApiStep: React.FC<Props> = ({ description, title }) => {
|
|
||||||
return (
|
|
||||||
<div className="gap-2 flex flex-col">
|
|
||||||
<span className="text-[#8A8A8A]">{title}</span>
|
|
||||||
<div className="flex flex-col gap-[10px] p-[18px] bg-[#F9F9F9] overflow-y-hidden">
|
|
||||||
<pre className="text-sm leading-5 text-black">{description}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -2,9 +2,8 @@ import { Product } from "@/_models/Product";
|
|||||||
import DownloadModelContent from "../DownloadModelContent";
|
import DownloadModelContent from "../DownloadModelContent";
|
||||||
import ModelDownloadButton from "../ModelDownloadButton";
|
import ModelDownloadButton from "../ModelDownloadButton";
|
||||||
import ModelDownloadingButton from "../ModelDownloadingButton";
|
import ModelDownloadingButton from "../ModelDownloadingButton";
|
||||||
import ViewModelDetailButton from "../ViewModelDetailButton";
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { modelDownloadStateAtom } from "@/_helpers/JotaiWrapper";
|
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product;
|
product: Product;
|
||||||
@ -36,8 +35,6 @@ const AvailableModelCard: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleViewDetails = () => {};
|
|
||||||
|
|
||||||
const downloadButton = isDownloading ? (
|
const downloadButton = isDownloading ? (
|
||||||
<div className="w-1/5 flex items-start justify-end">
|
<div className="w-1/5 flex items-start justify-end">
|
||||||
<ModelDownloadingButton total={total} value={transferred} />
|
<ModelDownloadingButton total={total} value={transferred} />
|
||||||
@ -50,7 +47,7 @@ const AvailableModelCard: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border rounded-lg border-gray-200">
|
<div className="border rounded-lg border-gray-200">
|
||||||
<div className="flex justify-between py-4 px-3 gap-[10px]">
|
<div className="flex justify-between py-4 px-3 gap-2.5">
|
||||||
<DownloadModelContent
|
<DownloadModelContent
|
||||||
required={required}
|
required={required}
|
||||||
author={product.author}
|
author={product.author}
|
||||||
|
|||||||
@ -1,20 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { useSetAtom } from "jotai";
|
||||||
currentConversationAtom,
|
|
||||||
showingAdvancedPromptAtom,
|
|
||||||
} from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useAtomValue, useSetAtom } from "jotai";
|
|
||||||
import SecondaryButton from "../SecondaryButton";
|
import SecondaryButton from "../SecondaryButton";
|
||||||
import SendButton from "../SendButton";
|
import SendButton from "../SendButton";
|
||||||
import { ProductType } from "@/_models/Product";
|
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom";
|
||||||
|
|
||||||
const BasicPromptAccessories: React.FC = () => {
|
const BasicPromptAccessories: React.FC = () => {
|
||||||
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
||||||
const currentConversation = useAtomValue(currentConversationAtom);
|
|
||||||
|
|
||||||
const shouldShowAdvancedPrompt = false;
|
const shouldShowAdvancedPrompt = false;
|
||||||
// currentConversation?.product.type === ProductType.ControlNet;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
||||||
import { showingAdvancedPromptAtom } from "@/_helpers/JotaiWrapper";
|
import { showingAdvancedPromptAtom } from "@/_helpers/atoms/Modal.atom";
|
||||||
|
|
||||||
const BasicPromptButton: React.FC = () => {
|
const BasicPromptButton: React.FC = () => {
|
||||||
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
const setShowingAdvancedPrompt = useSetAtom(showingAdvancedPromptAtom);
|
||||||
|
|||||||
@ -1,22 +1,45 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
|
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
|
||||||
|
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||||
|
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||||
|
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||||
|
import useInitModel from "@/_hooks/useInitModel";
|
||||||
import useSendChatMessage from "@/_hooks/useSendChatMessage";
|
import useSendChatMessage from "@/_hooks/useSendChatMessage";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
|
||||||
const BasicPromptInput: React.FC = () => {
|
const BasicPromptInput: React.FC = () => {
|
||||||
|
const activeConversationId = useAtomValue(getActiveConvoIdAtom);
|
||||||
|
const selectedModel = useAtomValue(selectedModelAtom);
|
||||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
|
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
|
||||||
const { sendChatMessage } = useSendChatMessage();
|
const { sendChatMessage } = useSendChatMessage();
|
||||||
|
const { requestCreateConvo } = useCreateConversation();
|
||||||
|
|
||||||
const handleMessageChange = (event: any) => {
|
const { initModel } = useInitModel();
|
||||||
|
|
||||||
|
const handleMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setCurrentPrompt(event.target.value);
|
setCurrentPrompt(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (event: any) => {
|
const handleKeyDown = async (
|
||||||
|
event: React.KeyboardEvent<HTMLTextAreaElement>
|
||||||
|
) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (!event.shiftKey) {
|
if (!event.shiftKey) {
|
||||||
|
if (activeConversationId) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
sendChatMessage();
|
sendChatMessage();
|
||||||
|
} else {
|
||||||
|
if (!selectedModel) {
|
||||||
|
console.log("No model selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestCreateConvo(selectedModel);
|
||||||
|
await initModel(selectedModel);
|
||||||
|
sendChatMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
|
|
||||||
import React, { PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
type PropType = PropsWithChildren<
|
|
||||||
React.DetailedHTMLProps<
|
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
HTMLButtonElement
|
|
||||||
>
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const PrevButton: React.FC<PropType> = (props) => {
|
|
||||||
const { children, ...restProps } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="embla__button embla__button--prev"
|
|
||||||
type="button"
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon width={20} height={20} />
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NextButton: React.FC<PropType> = (props) => {
|
|
||||||
const { children, ...restProps } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="embla__button embla__button--next"
|
|
||||||
type="button"
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
<ChevronRightIcon width={20} height={20} />
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { useTheme } from "next-themes";
|
|
||||||
import { SunIcon, MoonIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
export const ThemeChanger: React.FC = () => {
|
|
||||||
const { theme, setTheme, systemTheme } = useTheme();
|
|
||||||
const currentTheme = theme === "system" ? systemTheme : theme;
|
|
||||||
|
|
||||||
if (currentTheme === "dark") {
|
|
||||||
return (
|
|
||||||
<SunIcon
|
|
||||||
className="h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
onClick={() => setTheme("light")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MoonIcon
|
|
||||||
className="h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
onClick={() => setTheme("dark")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,29 +1,24 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useCallback, useRef, useState } from "react";
|
import React, { useCallback, useRef, useState, useEffect } from "react";
|
||||||
import ChatItem from "../ChatItem";
|
import ChatItem from "../ChatItem";
|
||||||
import { ChatMessage } from "@/_models/ChatMessage";
|
import { ChatMessage } from "@/_models/ChatMessage";
|
||||||
import useChatMessages from "@/_hooks/useChatMessages";
|
import useChatMessages from "@/_hooks/useChatMessages";
|
||||||
import {
|
|
||||||
chatMessages,
|
|
||||||
getActiveConvoIdAtom,
|
|
||||||
showingTyping,
|
|
||||||
} from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { selectAtom } from "jotai/utils";
|
import { selectAtom } from "jotai/utils";
|
||||||
import LoadingIndicator from "../LoadingIndicator";
|
import { getActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||||
|
import { chatMessages } from "@/_helpers/atoms/ChatMessage.atom";
|
||||||
|
|
||||||
const ChatBody: React.FC = () => {
|
const ChatBody: React.FC = () => {
|
||||||
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? "";
|
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? "";
|
||||||
const messageList = useAtomValue(
|
const messageList = useAtomValue(
|
||||||
selectAtom(
|
selectAtom(
|
||||||
chatMessages,
|
chatMessages,
|
||||||
useCallback((v) => v[activeConversationId], [activeConversationId])
|
useCallback((v) => v[activeConversationId], [activeConversationId]),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const [content, setContent] = useState<React.JSX.Element[]>([]);
|
const [content, setContent] = useState<React.JSX.Element[]>([]);
|
||||||
|
|
||||||
const isTyping = useAtomValue(showingTyping);
|
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
const { loading, hasMore } = useChatMessages(offset);
|
const { loading, hasMore } = useChatMessages(offset);
|
||||||
const intersectObs = useRef<any>(null);
|
const intersectObs = useRef<any>(null);
|
||||||
@ -42,10 +37,10 @@ const ChatBody: React.FC = () => {
|
|||||||
|
|
||||||
if (message) intersectObs.current.observe(message);
|
if (message) intersectObs.current.observe(message);
|
||||||
},
|
},
|
||||||
[loading, hasMore]
|
[loading, hasMore],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const list = messageList?.map((message, index) => {
|
const list = messageList?.map((message, index) => {
|
||||||
if (messageList?.length === index + 1) {
|
if (messageList?.length === index + 1) {
|
||||||
return (
|
return (
|
||||||
@ -60,11 +55,6 @@ const ChatBody: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col-reverse flex-1 py-4 overflow-y-auto scroll">
|
<div className="flex flex-col-reverse flex-1 py-4 overflow-y-auto scroll">
|
||||||
{isTyping && (
|
|
||||||
<div className="ml-4 mb-2" key="indicator">
|
|
||||||
<LoadingIndicator />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,22 +2,17 @@ import SimpleControlNetMessage from "../SimpleControlNetMessage";
|
|||||||
import SimpleImageMessage from "../SimpleImageMessage";
|
import SimpleImageMessage from "../SimpleImageMessage";
|
||||||
import SimpleTextMessage from "../SimpleTextMessage";
|
import SimpleTextMessage from "../SimpleTextMessage";
|
||||||
import { ChatMessage, MessageType } from "@/_models/ChatMessage";
|
import { ChatMessage, MessageType } from "@/_models/ChatMessage";
|
||||||
import StreamTextMessage from "../StreamTextMessage";
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { currentStreamingMessageAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
|
|
||||||
export default function renderChatMessage({
|
export default function renderChatMessage({
|
||||||
id,
|
id,
|
||||||
messageType,
|
messageType,
|
||||||
|
messageSenderType,
|
||||||
senderAvatarUrl,
|
senderAvatarUrl,
|
||||||
senderName,
|
senderName,
|
||||||
createdAt,
|
createdAt,
|
||||||
imageUrls,
|
imageUrls,
|
||||||
htmlText,
|
|
||||||
text,
|
text,
|
||||||
}: ChatMessage): React.ReactNode {
|
}: ChatMessage): React.ReactNode {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
const message = useAtomValue(currentStreamingMessageAtom);
|
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case MessageType.ImageWithText:
|
case MessageType.ImageWithText:
|
||||||
return (
|
return (
|
||||||
@ -42,22 +37,14 @@ export default function renderChatMessage({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case MessageType.Text:
|
case MessageType.Text:
|
||||||
return id !== message?.id ? (
|
return (
|
||||||
<SimpleTextMessage
|
<SimpleTextMessage
|
||||||
key={id}
|
key={id}
|
||||||
avatarUrl={senderAvatarUrl}
|
avatarUrl={senderAvatarUrl}
|
||||||
senderName={senderName}
|
senderName={senderName}
|
||||||
createdAt={createdAt}
|
createdAt={createdAt}
|
||||||
text={htmlText && htmlText.trim().length > 0 ? htmlText : text}
|
senderType={messageSenderType}
|
||||||
/>
|
text={text}
|
||||||
) : (
|
|
||||||
<StreamTextMessage
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
avatarUrl={senderAvatarUrl}
|
|
||||||
senderName={senderName}
|
|
||||||
createdAt={createdAt}
|
|
||||||
text={htmlText && htmlText.trim().length > 0 ? htmlText : text}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { MainViewState, getMainViewStateAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
import ModelManagement from "../ModelManagement";
|
|
||||||
import Welcome from "../WelcomeContainer";
|
|
||||||
import { Preferences } from "../Preferences";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatContainer({ children }: Props) {
|
|
||||||
const viewState = useAtomValue(getMainViewStateAtom);
|
|
||||||
|
|
||||||
switch (viewState) {
|
|
||||||
case MainViewState.ExploreModel:
|
|
||||||
return <ModelManagement />;
|
|
||||||
case MainViewState.Setting:
|
|
||||||
return <Preferences />;
|
|
||||||
case MainViewState.ResourceMonitor:
|
|
||||||
case MainViewState.MyModel:
|
|
||||||
case MainViewState.Welcome:
|
|
||||||
return <Welcome />;
|
|
||||||
default:
|
|
||||||
return <div className="flex flex-1 overflow-hidden">{children}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import {
|
|
||||||
getActiveConvoIdAtom,
|
|
||||||
setActiveConvoIdAtom,
|
|
||||||
} from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useAtomValue, useSetAtom } from "jotai";
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
imageUrl: string;
|
|
||||||
conversationId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CompactHistoryItem: React.FC<Props> = ({ imageUrl, conversationId }) => {
|
|
||||||
const activeConvoId = useAtomValue(getActiveConvoIdAtom);
|
|
||||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
|
||||||
|
|
||||||
const isSelected = activeConvoId === conversationId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveConvoId(conversationId)}
|
|
||||||
className={`${
|
|
||||||
isSelected ? "bg-gray-100" : "bg-transparent"
|
|
||||||
} w-14 h-14 rounded-lg`}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="rounded-full mx-auto"
|
|
||||||
src={imageUrl}
|
|
||||||
width={36}
|
|
||||||
height={36}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CompactHistoryItem;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { useAtomValue } from "jotai";
|
|
||||||
import CompactHistoryItem from "../CompactHistoryItem";
|
|
||||||
import { userConversationsAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
|
|
||||||
const CompactHistoryList: React.FC = () => {
|
|
||||||
const conversations = useAtomValue(userConversationsAtom);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col flex-1 gap-1 mt-3">
|
|
||||||
{conversations.map(({ id, image }) => (
|
|
||||||
<CompactHistoryItem
|
|
||||||
key={id}
|
|
||||||
conversationId={id ?? ""}
|
|
||||||
imageUrl={image ?? ""}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CompactHistoryList;
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import JanImage from "../JanImage";
|
import JanImage from "../JanImage";
|
||||||
import { setActiveConvoIdAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
|
import { setActiveConvoIdAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||||
|
|
||||||
const CompactLogo: React.FC = () => {
|
const CompactLogo: React.FC = () => {
|
||||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import CompactHistoryList from "../CompactHistoryList";
|
|
||||||
import CompactLogo from "../CompactLogo";
|
|
||||||
|
|
||||||
const CompactSideBar: React.FC = () => (
|
|
||||||
<div className="h-screen w-16 border-r border-gray-300 flex flex-col items-center pt-3 gap-3">
|
|
||||||
<CompactLogo />
|
|
||||||
<CompactHistoryList />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default CompactSideBar;
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/JotaiWrapper";
|
import { showConfirmDeleteConversationModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||||
import useDeleteConversation from "@/_hooks/useDeleteConversation";
|
import useDeleteConversation from "@/_hooks/useDeleteConversation";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
@ -63,8 +63,8 @@ const ConfirmDeleteConversationModal: React.FC = () => {
|
|||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Are you sure you want to delete this conversation? All
|
Are you sure you want to delete this conversation? All
|
||||||
of messages will be permanently removed from our servers
|
of messages will be permanently removed. This action
|
||||||
forever. This action cannot be undone.
|
cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
||||||
import { showConfirmDeleteModalAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import useSignOut from "@/_hooks/useSignOut";
|
import { showConfirmDeleteModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||||
|
|
||||||
const ConfirmDeleteModelModal: React.FC = () => {
|
const ConfirmDeleteModelModal: React.FC = () => {
|
||||||
const [show, setShow] = useAtom(showConfirmDeleteModalAtom);
|
const [show, setShow] = useAtom(showConfirmDeleteModalAtom);
|
||||||
const { signOut } = useSignOut();
|
|
||||||
|
|
||||||
const onLogOutClick = () => {
|
const onConfirmDelete = () => {};
|
||||||
signOut().then(() => setShow(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={show} as={Fragment}>
|
<Transition.Root show={show} as={Fragment}>
|
||||||
@ -65,7 +61,7 @@ const ConfirmDeleteModelModal: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
|
className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"
|
||||||
onClick={onLogOutClick}
|
onClick={onConfirmDelete}
|
||||||
>
|
>
|
||||||
Log out
|
Log out
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";
|
||||||
import { showConfirmSignOutModalAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import useSignOut from "@/_hooks/useSignOut";
|
import useSignOut from "@/_hooks/useSignOut";
|
||||||
|
import { showConfirmSignOutModalAtom } from "@/_helpers/atoms/Modal.atom";
|
||||||
|
|
||||||
const ConfirmSignOutModal: React.FC = () => {
|
const ConfirmSignOutModal: React.FC = () => {
|
||||||
const [show, setShow] = useAtom(showConfirmSignOutModalAtom);
|
const [show, setShow] = useAtom(showConfirmSignOutModalAtom);
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const ConversationalCard: React.FC<Props> = ({ product }) => {
|
|||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="flex text-xs leading-5 text-gray-500 items-center gap-[2px]">
|
<span className="flex text-xs leading-5 text-gray-500 items-center gap-0.5">
|
||||||
<Image src={"icons/play.svg"} width={16} height={16} alt="" />
|
<Image src={"icons/play.svg"} width={16} height={16} alt="" />
|
||||||
32.2k runs
|
32.2k runs
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import { ApiStep } from "../ApiStep";
|
|
||||||
|
|
||||||
const DescriptionPane: React.FC = () => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
title: "Install the Node.js client:",
|
|
||||||
description: "npm install replicate",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title:
|
|
||||||
"Next, copy your API token and authenticate by setting it as an environment variable:",
|
|
||||||
description:
|
|
||||||
"export REPLICATE_API_TOKEN=r8_*************************************",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "lorem ipsum dolor asimet",
|
|
||||||
description: "come codes here",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4 w-[full]">
|
|
||||||
<h2 className="text-[20px] tracking-[-0.4px] leading-[25px]">
|
|
||||||
Run the model
|
|
||||||
</h2>
|
|
||||||
{data.map((item, index) => (
|
|
||||||
<ApiStep key={index} {...item} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DescriptionPane;
|
|
||||||
@ -18,19 +18,19 @@ const DownloadModelContent: React.FC<Props> = ({
|
|||||||
type,
|
type,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-4/5 flex flex-col gap-[10px]">
|
<div className="w-4/5 flex flex-col gap-2.5">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<h2 className="font-medium text-xl leading-[25px] tracking-[-0.4px] text-gray-900">
|
<h2 className="font-medium text-xl leading-[25px] tracking-[-0.4px] text-gray-900">
|
||||||
{name}
|
{name}
|
||||||
</h2>
|
</h2>
|
||||||
<DownloadModelTitle title={type} />
|
<DownloadModelTitle title={type} />
|
||||||
<div className="py-[2px] px-[10px] bg-purple-100 rounded-md text-center">
|
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||||
<span className="text-xs leading-[18px] font-semibold text-purple-800">
|
<span className="text-xs leading-[18px] font-semibold text-purple-800">
|
||||||
{author}
|
{author}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{required && (
|
{required && (
|
||||||
<div className="py-[2px] px-[10px] bg-purple-100 rounded-md text-center">
|
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||||
<span className="text-xs leading-[18px] text-[#11192899]">
|
<span className="text-xs leading-[18px] text-[#11192899]">
|
||||||
Required{" "}
|
Required{" "}
|
||||||
</span>
|
</span>
|
||||||
@ -44,7 +44,7 @@ const DownloadModelContent: React.FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
isRecommend ? "flex" : "hidden"
|
isRecommend ? "flex" : "hidden"
|
||||||
} w-fit justify-center items-center bg-green-50 rounded-full px-[10px] py-[2px] gap-2`}
|
} w-fit justify-center items-center bg-green-50 rounded-full px-2.5 py-0.5 gap-2`}
|
||||||
>
|
>
|
||||||
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||||||
<span className="text-green-600 font-medium text-xs leading-18px">
|
<span className="text-green-600 font-medium text-xs leading-18px">
|
||||||
|
|||||||