docs: sync updated content from dev to docs branch (#2283)
docs: sync updated content from dev to docs branch
This commit is contained in:
commit
2b26d4af55
4
.github/scripts/auto-sign.sh
vendored
4
.github/scripts/auto-sign.sh
vendored
@ -7,6 +7,6 @@ if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then
|
||||
fi
|
||||
|
||||
# If both variables are set, execute the following commands
|
||||
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
|
||||
find "$APP_PATH" -type f -name "*.o" -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
find "$APP_PATH" -type f -name "*.o" -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
|
||||
@ -78,6 +78,10 @@ jobs:
|
||||
|
||||
jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
cat electron/package.json
|
||||
|
||||
- name: Update app version base on tag
|
||||
@ -91,6 +95,9 @@ jobs:
|
||||
mv /tmp/package.json electron/package.json
|
||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
cat electron/package.json
|
||||
env:
|
||||
VERSION_TAG: ${{ inputs.new_version }}
|
||||
|
||||
|
||||
@ -72,6 +72,10 @@ jobs:
|
||||
|
||||
jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
cat electron/package.json
|
||||
|
||||
- name: Update app version base on tag
|
||||
@ -85,6 +89,9 @@ jobs:
|
||||
mv /tmp/package.json electron/package.json
|
||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
cat electron/package.json
|
||||
env:
|
||||
VERSION_TAG: ${{ inputs.new_version }}
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ electron/renderer
|
||||
electron/models
|
||||
electron/docs
|
||||
electron/engines
|
||||
electron/playwright-report
|
||||
server/pre-install
|
||||
package-lock.json
|
||||
|
||||
|
||||
20
README.md
20
README.md
@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-win-x64-0.4.7.exe'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-win-x64-0.4.8.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-x64-0.4.7.dmg'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-x64-0.4.8.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-arm64-0.4.7.dmg'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-arm64-0.4.8.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-amd64-0.4.7.deb'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-amd64-0.4.8.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-x86_64-0.4.7.AppImage'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-x86_64-0.4.8.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.7-300.exe'>
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.8-310.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.7-300.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.8-310.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.7-300.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.8-310.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.7-300.deb'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.8-310.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.7-300.AppImage'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.8-310.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
|
||||
@ -45,19 +45,20 @@
|
||||
"start": "rollup -c rollup.config.ts -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^25.4.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^12.0.2",
|
||||
"eslint-plugin-jest": "^23.8.2",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint": "8.57.0",
|
||||
"rollup": "^2.38.5",
|
||||
"rollup-plugin-commonjs": "^9.1.8",
|
||||
"rollup-plugin-json": "^3.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"ts-jest": "^26.1.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.3.3",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,14 @@ export enum NativeRoute {
|
||||
selectDirectory = 'selectDirectory',
|
||||
selectModelFiles = 'selectModelFiles',
|
||||
relaunch = 'relaunch',
|
||||
|
||||
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||
sendQuickAskInput = 'sendQuickAskInput',
|
||||
|
||||
hideMainWindow = 'hideMainWindow',
|
||||
showMainWindow = 'showMainWindow',
|
||||
|
||||
quickAskSizeUpdated = 'quickAskSizeUpdated',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +39,9 @@ export enum AppEvent {
|
||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||
|
||||
onUserSubmitQuickAsk = 'onUserSubmitQuickAsk',
|
||||
onSelectedText = 'onSelectedText',
|
||||
}
|
||||
|
||||
export enum DownloadRoute {
|
||||
|
||||
@ -41,7 +41,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const modelFolderFullPath = join(janDataFolderPath, 'models', modelId)
|
||||
|
||||
if (!fs.existsSync(modelFolderFullPath)) {
|
||||
throw `Model not found: ${modelId}`
|
||||
throw new Error(`Model not found: ${modelId}`)
|
||||
}
|
||||
|
||||
const files: string[] = fs.readdirSync(modelFolderFullPath)
|
||||
@ -53,7 +53,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const modelMetadata: Model = JSON.parse(fs.readFileSync(modelMetadataPath, 'utf-8'))
|
||||
|
||||
if (!ggufBinFile) {
|
||||
throw 'No GGUF model file found'
|
||||
throw new Error('No GGUF model file found')
|
||||
}
|
||||
const modelBinaryPath = join(modelFolderFullPath, ggufBinFile)
|
||||
|
||||
@ -76,7 +76,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const promptTemplate = modelMetadata.settings.prompt_template
|
||||
const prompt = promptTemplateConverter(promptTemplate)
|
||||
if (prompt?.error) {
|
||||
return Promise.reject(prompt.error)
|
||||
throw new Error(prompt.error)
|
||||
}
|
||||
nitroModelSettings.system_prompt = prompt.system_prompt
|
||||
nitroModelSettings.user_prompt = prompt.user_prompt
|
||||
|
||||
7
core/src/types/miscellaneous/appUpdate.ts
Normal file
7
core/src/types/miscellaneous/appUpdate.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type AppUpdateInfo = {
|
||||
total: number
|
||||
delta: number
|
||||
transferred: number
|
||||
percent: number
|
||||
bytesPerSecond: number
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './systemResourceInfo'
|
||||
export * from './promptTemplate'
|
||||
export * from './appUpdate'
|
||||
|
||||
@ -138,3 +138,7 @@ export type ModelRuntimeParams = {
|
||||
presence_penalty?: number
|
||||
engine?: string
|
||||
}
|
||||
|
||||
export type ModelInitFailed = Model & {
|
||||
error: Error
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: About Jan
|
||||
slug: /about
|
||||
description: Jan is a productivity tool to customize AI to your needs and workflows.
|
||||
description: Jan is a desktop application that turns computers into thinking machines.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -12,45 +12,74 @@ keywords:
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
about Jan,
|
||||
desktop application,
|
||||
thinking machine,
|
||||
]
|
||||
---
|
||||
|
||||
Jan is a [open-source](https://en.wikipedia.org/wiki/Open_source), [local-first](https://www.inkandswitch.com/local-first/) tool to [create, customize and use AI](https://www.gatesnotes.com/AI-agents) for everyday tasks.
|
||||
Jan turns computers into a thinking machine to change how you use computers.
|
||||
Jan is created and maintained by Jan Labs, a robotics company.
|
||||
|
||||
You can:
|
||||
With Jan, you can:
|
||||
|
||||
- Run locally using [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/)
|
||||
- Fine-tune AI with specific knowledge
|
||||
- Search the web and other databases
|
||||
- Connect AI to your everyday tools and (with your permission) do work on your behalf
|
||||
- Run [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) locally or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/).
|
||||
- Fine-tune AI with specific knowledge.
|
||||
- Supercharge your productivity by leveraging AI.
|
||||
- Search the web and databases.
|
||||
- Integrate AI with everyday tools to work on your behalf (with permission).
|
||||
- Customize and add features with Extensions.
|
||||
|
||||
Longer-term, Jan is building a cognitive framework for future robots. We envision a world where we have personal or company robots that we continually improve and customize, growing together with us.
|
||||
:::tip
|
||||
|
||||
Jan aims for long-term human-robot collaboration, envisioning AI as a harmonious extension of human capabilities. Our goal is to build customizable robots that we continually improve and customize, growing together.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## Why do we exist
|
||||
## Jan’s principles
|
||||
|
||||
At Jan, our mission is to advance human-machine collaboration. We achieve this through delivering the best open-source, local-first tools to allow users to run, customize and tinker with AI.
|
||||
- **Ownership**: Jan is committed to developing a product that fully belongs to users. You're the true owner, free from data tracking and storage by us.
|
||||
- **Privacy**: Jan works locally by default, allowing use without an internet connection. Your data stays on your device in a universal format, giving you complete privacy control.
|
||||
- **100% User Supported**: Every user can access, develop, and customize Jan's codebases to suit their needs.
|
||||
- **Rejecting Dark Patterns**: We never use tricks to extract more money or lock you into an ecosystem.
|
||||
|
||||
## What's different about it?
|
||||
## Why do we exist?
|
||||
|
||||
> _"I do not fear computers. I fear the lack of them." - Isaac Asimov_
|
||||
|
||||
Jan was founded on the belief that AI should coexist with humans, not replace them. Our mission is to democratize AI access, ensuring everyone can easily utilize it with full ownership and control over their data, free from privacy concerns.
|
||||
|
||||
### What are the things Jan committed on?
|
||||
|
||||
We are committed to creating open, local-first products that extend individual freedom, rejecting dark patterns and ecosystem lock-ins, and embracing an open-source ethos.
|
||||
|
||||
#### What's different about it?
|
||||
|
||||
| | Status Quo | Jan |
|
||||
| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Ownership | AI Monopolies owned by Big Tech | AI that you own and control |
|
||||
| Openness? | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
||||
| Your role | Consume | Create, Tinker and Customize |
|
||||
| Approach | Cloud | [Local-first](https://www.inkandswitch.com/local-first/), running 100% on your devices |
|
||||
| Data | Data stored on their servers | Data stored in your local filesystem in open, non-proprietary file formats |
|
||||
| Privacy | 😂 | Runs 100% on your own machine, predictably, privately and offline |
|
||||
| Transparency | "Black Box" | Runs predictability with code available to tinker and customize |
|
||||
| What happens if there's an outage or goes out of business? | Your life's work held hostage in the cloud in proprietary data formats[^1] | Continues to run 100% on your computer, your data is safe in your local folder |
|
||||
| Driving Philosophy | Monetize your users | [Privacy as a human right](https://en.wikipedia.org/wiki/Right_to_privacy) and the [Right to Repair](https://www.repair.org/) |
|
||||
| --------------------- | -------------------------- | ---------------------------------------------------------------------- |
|
||||
| **Ownership** | Owned by Big Tech | Fully owned by you |
|
||||
| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
||||
| **Your Role** | Consumer | Creator |
|
||||
| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) |
|
||||
| **Data Handling** | Stored on external servers | Stored locally, openly accessible |
|
||||
| **Privacy** | Questionable | Private and offline |
|
||||
| **Transparency** | Opaque "Black Box" | Open-source and customizable |
|
||||
| **Outage Resilience** | Potential data hostage | Continues to work on your device |
|
||||
| **Philosophy** | User monetization | Empowerment with the right to repair |
|
||||
|
||||
## How do I get it?
|
||||
## How we work
|
||||
|
||||
You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
||||
Jan is an open-source product with transparent development and future features. Users have the right to modify and customize Jan. We are committed to building an open-source AI ecosystem.
|
||||
|
||||
You can read the [User Guide](/docs/user-guide) if you need some help to get started.
|
||||
Jan is building in public using GitHub, where anyone is welcome to join. Key resources include Jan's [Kanban](https://github.com/orgs/janhq/projects/5/views/7) and Jan's [Roadmap](https://github.com/orgs/janhq/projects/5/views/29).
|
||||
|
||||
Jan has a fully-remote team, primarily based in the APAC timezone, and we use Discord and GitHub for collaboration. Our community is central to our operations, and we embrace asynchronous work. We hold meetings only for synchronization and vision sharing, using [Excalidraw](https://excalidraw.com/) or [Miro](https://miro.com/) for visualization and sharing notes on Discord for alignment. We also use [HackMD](https://hackmd.io/) to document our ideas and build a Jan library.
|
||||
|
||||
## How to get it?
|
||||
|
||||
You can install and start using Jan in less than 5 minutes, from [Jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
||||
|
||||
## What license is the code under?
|
||||
|
||||
@ -58,8 +87,6 @@ Jan is licensed under the [AGPLv3 License](https://github.com/janhq/jan/blob/mai
|
||||
|
||||
We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2].
|
||||
|
||||
We also have a [Contributor Program](/docs/team/contributor-program) to provide ownership and upside to contributors who have made significant contributions to the project.
|
||||
|
||||
## What was it built with?
|
||||
|
||||
[Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]).
|
||||
@ -73,11 +100,9 @@ We follow [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/
|
||||
|
||||
Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale.
|
||||
|
||||
:::tip
|
||||
## Join the team
|
||||
|
||||
**At its core, Jan is a software development kit to build and run copilots on personal devices**. The Desktop Client many folks use is, rather, a specific set of extensions packaged by default. We're excited to see what developers do with the SDK (once its in better shape).
|
||||
|
||||
:::
|
||||
Join us on this journey at Jan Labs, where we embrace open-source collaboration and transparency. Together, let's shape a future where Jan becomes an essential companion in the open-source community. Explore [careers](https://janai.bamboohr.com/careers) with us.
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
31
docs/docs/guides/error-codes/no-assistant-available.mdx
Normal file
31
docs/docs/guides/error-codes/no-assistant-available.mdx
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: No Assistant Available
|
||||
sidebar_position: 7
|
||||
description: Troubleshooting steps to resolve issues no assistant available.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
troubleshooting,
|
||||
no assistant available,
|
||||
]
|
||||
---
|
||||
|
||||
When you encounter the following error message:
|
||||
```
|
||||
No assistant available.
|
||||
```
|
||||
|
||||
This issue arises when a new, unintentional file appears in `/jan/assistants`.
|
||||
|
||||
It can be resolved through the following steps:
|
||||
|
||||
1. Access the `/jan/assistants` directory using a file manager or terminal.
|
||||
|
||||
2. Within `/jan/assistants`, this directory should only contain a folder named `jan`. Identify any file outside of this folder and remove it.
|
||||
62
docs/docs/guides/error-codes/stuck-on-loading-model.mdx
Normal file
62
docs/docs/guides/error-codes/stuck-on-loading-model.mdx
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Stuck on Loading Model
|
||||
sidebar_position: 8
|
||||
description: Troubleshooting steps to resolve issues related to the loading model.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
troubleshooting,
|
||||
stuck on loading model,
|
||||
]
|
||||
---
|
||||
|
||||
## 1. Issue: Model Loading Stuck Due To Missing Windows Management Instrumentation Command-line (WMIC)
|
||||
|
||||
Encountering a stuck-on-loading model issue in Jan is caused by errors related to the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable.
|
||||
|
||||
Error message:
|
||||
```
|
||||
index.js:47 Uncaught (in promise) Error: Error invoking remote method 'invokeExtensionFunc': Error: Command failed: WMIC CPU Get NumberOfCores
|
||||
```
|
||||
|
||||
It can be resolved through the following steps:
|
||||
|
||||
1. **Open System Properties:**
|
||||
- Press `Windows key + R`.
|
||||
- Type `sysdm.cpl` and press `Enter`.
|
||||
|
||||
2. **Access Environment Variables:**
|
||||
- Go to the "Advanced" tab.
|
||||
- Click the "Environment Variables" button.
|
||||
|
||||
3. **Edit System PATH:**
|
||||
- Under "System Variables" find and select `Path`.
|
||||
- Click "Edit."
|
||||
|
||||
4. **Add WMIC Path:**
|
||||
- Click "New" and enter `C:\Windows\System32\Wbem`.
|
||||
|
||||
5. **Save Changes:**
|
||||
- Click "OK" to close and save your changes.
|
||||
|
||||
6. **Verify Installation:**
|
||||
- Restart any command prompts or terminals.
|
||||
- Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`.
|
||||
|
||||
|
||||
## 2. Issue: Model Loading Stuck Due To CPU Without AVX
|
||||
|
||||
Encountering an issue with models stuck on loading in Jan can be due to the use of older generation CPUs that do not support Advanced Vector Extensions (AVX).
|
||||
|
||||
To check if your CPU supports AVX, visit the following link: [CPUs with AVX](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX)
|
||||
|
||||
:::warning [Please use this with caution]
|
||||
As a workaround, consider using an [emulator](https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html) to simulate AVX support.
|
||||
:::
|
||||
26
docs/docs/guides/error-codes/thread-disappreance.mdx
Normal file
26
docs/docs/guides/error-codes/thread-disappreance.mdx
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Thread Disappearance
|
||||
sidebar_position: 6
|
||||
description: Troubleshooting steps to resolve issues threads suddenly disappearance.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
troubleshooting,
|
||||
thread disappearance,
|
||||
]
|
||||
---
|
||||
|
||||
When you encounter the error of old threads suddenly disappear. This can happen when a new, unintentional file is created in `/jan/threads`.
|
||||
|
||||
It can be resolved through the following steps:
|
||||
|
||||
1. Go to `/jan/threads`.
|
||||
|
||||
2. The `/jan/threads` directory contains many folders named with the prefix `jan_` followed by an ID (e.g., `jan_123`). Look for any file not conforming to this naming pattern and remove it.
|
||||
@ -17,6 +17,83 @@ keywords:
|
||||
]
|
||||
---
|
||||
|
||||
:::caution
|
||||
This is currently under development.
|
||||
## General Issues
|
||||
|
||||
- **Why can't I download models like Pandora 11B Q4 and Solar Instruct 10.7B Q4?**
|
||||
- These models might have been removed or taken down. Please check the [Pre-configured Models](models-list.mdx) for the latest updates on model availability.
|
||||
|
||||
- **Why does Jan display "Apologies, something's amiss" when I try to run it?**
|
||||
- This issue may arise if you're using an older Intel chip that does not fully support AVX instructions required for running AI models. Upgrading your hardware may resolve this issue.
|
||||
|
||||
- **How can I use Jan in Russia?**
|
||||
- To use Jan in Russia, a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) is recommended to bypass any regional restrictions that might be in place.
|
||||
|
||||
- **I'm experiencing an error on startup from Nitro. What should I do?**
|
||||
- If you encounter errors with Nitro, try switching the path to use the Nitro executable for the version 12-0. This adjustment can help resolve path-related issues.
|
||||
|
||||
## Download and Installation Issues
|
||||
|
||||
- **What does "Error occurred: Unexpected token" mean?**
|
||||
- This error usually indicates a problem with your internet connection or that your access to certain resources is being blocked. Using a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) can help avoid these issues by providing a secure and unrestricted internet connection.
|
||||
|
||||
- **Why aren't my downloads working?**
|
||||
- If you're having trouble downloading directly through Jan, you might want to download the model separately and then import it into Jan. Detailed instructions are available on [here](install.mdx).
|
||||
|
||||
- **Jan AI doesn't open on my Mac with an Intel processor. What can I do?**
|
||||
- Granting the `.npm` folder permission for the user can resolve issues related to permissions on macOS, especially for users with Intel processors.
|
||||
|
||||
- **What should I do if the model download freezes?**
|
||||
- If a model download freezes, consider importing the models manually. You can find more detailed guidance on how to do this at [Manual Import](./models/import-models.mdx) article.
|
||||
|
||||
- **I received a message that the model GPT4 does not exist or I do not have access. What should I do?**
|
||||
- This message typically means you need to top up your credit with OpenAI or check your access permissions for the model.
|
||||
|
||||
- **I can't download models from "Explore the Hub." What's the solution?**
|
||||
- Uninstalling Jan, clearing the cache, and reinstalling it following the guide provided [here](install.mdx) may help. Also, consider downloading the `.gguf` model via a browser as an alternative approach.
|
||||
|
||||
## Technical Issues and Solutions
|
||||
|
||||
- **How can I download models with a socks5 proxy or import a local model file?**
|
||||
- Nightly builds of Jan offer support for downloading models with socks5 proxies or importing local model files.
|
||||
|
||||
- **My device shows no GPU usage and lacks a Settings folder. What should I do?**
|
||||
- Using the nightly builds of Jan can address issues related to GPU usage and the absence of a Settings folder, as these builds contain the latest fixes and features.
|
||||
|
||||
- **Why does Jan display a toast message saying a model is loaded when it is not actually loaded?**
|
||||
- This issue can be resolved by downloading the `.gguf` file from Hugging Face and replacing it in the model folder. This ensures the correct model is loaded.
|
||||
|
||||
- **How to enable CORS when running Nitro?**
|
||||
- By default, CORS (Cross-Origin Resource Sharing) is disabled when running Nitro. Enabling CORS can be necessary for certain operations and integrations. Check the official documentation for instructions on how to enable CORS if your workflow requires it.
|
||||
|
||||
## Compatibility and Support
|
||||
|
||||
- **How to use GPU AMD for Jan?**
|
||||
- Jan now supports AMD GPUs through Vulkan. This enhancement allows users with AMD graphics cards to leverage GPU acceleration, improving performance for AI model computations.
|
||||
|
||||
- **Is Jan available for Android or iOS?**
|
||||
- Jan is primarily focused on the Desktop app and does not currently offer mobile apps for Android or iOS. The development team is concentrating on enhancing the desktop experience.
|
||||
|
||||
## Development and Features
|
||||
|
||||
- **Does Jan support Safetensors?**
|
||||
- At the moment, Jan only supports GGUF. However, there are plans to support `.safetensor` files in the future.
|
||||
|
||||
- **I hope to customize the installation path of each model. Is that possible?**
|
||||
- Yes you can customize the installation path. Please see [here](https://jan.ai/guides/advanced-settings/#access-the-jan-data-folder) for more information.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **What should I do if there's high CPU usage while Jan is idle?**
|
||||
- If you notice high CPU usage while Jan is idle, consider using the nightly builds of Jan
|
||||
|
||||
- **What does the error "Failed to fetch" mean, and how can I fix it?**
|
||||
- The "Failed to fetch" error typically occurs due to network issues or restrictions. Using the nightly builds of Jan may help overcome these issues by providing updated fixes and features.
|
||||
|
||||
- **What should I do if "Failed to fetch" occurs using MacBook Pro with Intel HD Graphics 4000 1536 MB?**
|
||||
- Ensure that the model size is less than 90% of your available VRAM and that the VRAM is accessible to the app. Managing the resources effectively can help mitigate this issue.
|
||||
|
||||
:::info[Assistance and Support]
|
||||
|
||||
If you have questions, please join our [Discord community](https://discord.gg/Dt7MxDyNNZ) for support, updates, and discussions.
|
||||
|
||||
:::
|
||||
84
docs/docs/guides/integration/groq.mdx
Normal file
84
docs/docs/guides/integration/groq.mdx
Normal file
@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Groq
|
||||
sidebar_position: 10
|
||||
slug: /guides/integration/groq
|
||||
description: Learn how to integrate Groq API with Jan for enhanced functionality.
|
||||
keywords:
|
||||
[
|
||||
Groq API,
|
||||
Jan,
|
||||
Jan AI,
|
||||
ChatGPT alternative,
|
||||
conversational AI,
|
||||
large language model,
|
||||
integration,
|
||||
Groq integration,
|
||||
API integration
|
||||
]
|
||||
---
|
||||
|
||||
## How to Integrate Mistral AI with Jan
|
||||
|
||||
This guide provides step-by-step instructions on integrating the Groq API with Jan, enabling users to leverage Groq's capabilities within Jan's conversational interface.
|
||||
|
||||
Before proceeding, ensure you have the following:
|
||||
- Access to the Jan Application
|
||||
- Groq API credentials
|
||||
|
||||
## Integration Steps
|
||||
|
||||
### Step 1: Obtain Groq API Credentials
|
||||
|
||||
If you haven't already, sign up for the Groq API and obtain your API credentials.
|
||||
Obtain Groq API keys from your [Groq Console](https://console.groq.com/keys).
|
||||
|
||||
### Step 2: Configure Jan Settings
|
||||
|
||||
1. Insert the Groq AI API key into `~/jan/engines/openai.json`.
|
||||
|
||||
```json title="~/jan/engines/openai.json"
|
||||
{
|
||||
"full_url": "https://api.groq.com/openai/v1/chat/completions",
|
||||
"api_key": "<your-groq-api-key>"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Enable Groq Integration
|
||||
|
||||
To set up the configuration for Groq in Jan, follow these steps:
|
||||
|
||||
1. Navigate to `~/jan/models`.
|
||||
2. Create a folder named `groq`.
|
||||
3. Inside the groq folder, create a model.json file with the specified settings:
|
||||
```json title="~/jan/models/groq/model.json
|
||||
{
|
||||
"id": "mixtral-8x7b-32768",
|
||||
"object": "model",
|
||||
"name": "Groq Integration",
|
||||
"version": "1.0",
|
||||
"description": "Integration with Groq API for enhanced functionality.",
|
||||
"format": "api",
|
||||
"sources": [],
|
||||
"settings": {},
|
||||
"parameters": {},
|
||||
"metadata": {
|
||||
"author": "Mistral",
|
||||
"tags": ["Groq Integration"]
|
||||
},
|
||||
"engine": "openai"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Start the Model
|
||||
|
||||
1. Restart Jan and navigate to the **Hub**.
|
||||
2. Locate your model and click the **Use** button.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues during the integration process or while using Groq with Jan, consider the following troubleshooting steps:
|
||||
|
||||
- Double-check your API credentials and ensure they are correctly entered.
|
||||
- Verify that the Groq integration is enabled within Jan's settings.
|
||||
- Check for any error messages or logs that may provide insight into the issue.
|
||||
- Reach out to Groq API support for assistance if needed.
|
||||
@ -1,6 +1,5 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
||||
@ -105,6 +104,9 @@ const config = {
|
||||
{
|
||||
from: "/troubleshooting/undefined-issue/",
|
||||
to: "/guides/error-codes/undefined-issue/",
|
||||
}, {
|
||||
from: "/install/",
|
||||
to: "/guides/install/",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -123,7 +125,7 @@ const config = {
|
||||
docs: {
|
||||
routeBasePath: "/",
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
editUrl: "https://github.com/janhq/jan/tree/main/docs",
|
||||
editUrl: "https://github.com/janhq/jan/tree/dev/docs",
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
},
|
||||
|
||||
122
docs/sidebars.js
122
docs/sidebars.js
@ -150,10 +150,128 @@ const sidebars = {
|
||||
],
|
||||
},
|
||||
],
|
||||
// guidesSidebar: [
|
||||
// {
|
||||
// type: "autogenerated",
|
||||
// dirName: "guides",
|
||||
// },
|
||||
// ],
|
||||
guidesSidebar: [
|
||||
{
|
||||
type: "autogenerated",
|
||||
dirName: "guides",
|
||||
type: "category",
|
||||
label: "Get Started",
|
||||
collapsible: false,
|
||||
className: "head_Menu",
|
||||
items: [
|
||||
"guides/quickstart",
|
||||
"guides/install",
|
||||
"guides/start-server",
|
||||
"guides/models-list"
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Guides",
|
||||
collapsible: false,
|
||||
className: "head_Menu",
|
||||
items: [
|
||||
"guides/best-practices",
|
||||
"guides/thread",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Advanced Features",
|
||||
collapsible: false,
|
||||
className: "head_Menu",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "Advanced Model Setup",
|
||||
className: "head_SubMenu",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: "guides/models/README",
|
||||
},
|
||||
items: [
|
||||
"guides/models/customize-engine",
|
||||
"guides/models/import-models",
|
||||
"guides/models/integrate-remote",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Extensions",
|
||||
className: "head_SubMenu",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: "guides/extensions/README",
|
||||
},
|
||||
items: [
|
||||
"guides/extensions/import-ext",
|
||||
"guides/extensions/setup-ext",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Integrations",
|
||||
className: "head_SubMenu",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: "guides/integration/README",
|
||||
},
|
||||
items: [
|
||||
"guides/integration/azure",
|
||||
"guides/integration/discord",
|
||||
"guides/integration/groq",
|
||||
"guides/integration/lmstudio",
|
||||
"guides/integration/mistral",
|
||||
"guides/integration/ollama",
|
||||
"guides/integration/openinterpreter",
|
||||
"guides/integration/openrouter",
|
||||
"guides/integration/raycast",
|
||||
"guides/integration/vscode",
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Troubleshooting",
|
||||
collapsible: false,
|
||||
className: "head_Menu",
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
label: "Error Codes",
|
||||
className: "head_SubMenu",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: "guides/error-codes/README",
|
||||
},
|
||||
items: [
|
||||
"guides/error-codes/how-to-get-error-logs",
|
||||
"guides/error-codes/permission-denied",
|
||||
"guides/error-codes/something-amiss",
|
||||
"guides/error-codes/undefined-issue",
|
||||
"guides/error-codes/unexpected-token",
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Common Error",
|
||||
className: "head_SubMenu",
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: "guides/common-error/README",
|
||||
},
|
||||
items: [
|
||||
"guides/common-error/broken-build",
|
||||
"guides/common-error/not-using-gpu",
|
||||
]
|
||||
},
|
||||
"guides/faq"
|
||||
]
|
||||
},
|
||||
],
|
||||
developerSidebar: [
|
||||
|
||||
@ -1,8 +1,56 @@
|
||||
/* Hide descriptions in cards without a description */
|
||||
.DocCardList--no-description .card p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* For dark theme */
|
||||
[data-theme="dark"] .DocSearch {
|
||||
[data-theme='dark'] .DocSearch {
|
||||
--docsearch-hit-active-color: #090a11; /* Keep the color unchanged */
|
||||
}
|
||||
/* Sidebar styles based on Docusaurus light theme */
|
||||
[data-theme='light'] .head_Menu div {
|
||||
font-weight: bold;
|
||||
background-color: var(--ifm-background-color);
|
||||
margin-left: 0.7rem;
|
||||
font-size: larger;
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
[data-theme='light'] .head_Menu li {
|
||||
font-weight: normal;
|
||||
background-color: var(--ifm-background-color);
|
||||
margin-bottom: 5px;
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
[data-theme='light'] .head_SubMenu div {
|
||||
font-weight: normal;
|
||||
background-color: var(--ifm-background-color);
|
||||
margin-left: 0rem;
|
||||
font-size: medium;
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
/* Dark mode styles based on Docusaurus dark theme */
|
||||
[data-theme='dark'] .head_Menu div {
|
||||
font-weight: bold;
|
||||
background-color: var(--ifm-background-color);
|
||||
color: var(--ifm-font-color-base);
|
||||
margin-left: 0.7rem;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .head_Menu li {
|
||||
font-weight: normal;
|
||||
background-color: var(--ifm-background-color);
|
||||
margin-bottom: 5px;
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .head_SubMenu div {
|
||||
font-weight: normal;
|
||||
background-color: var(--ifm-background-color);
|
||||
color: var(--ifm-font-color-base);
|
||||
margin-left: 0rem;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
@ -1,25 +1,20 @@
|
||||
import { Handler, RequestHandler } from '@janhq/core/node'
|
||||
import { ipcMain } from 'electron'
|
||||
import { WindowManager } from '../managers/window'
|
||||
import { windowManager } from '../managers/window'
|
||||
|
||||
export function injectHandler() {
|
||||
const ipcWrapper: Handler = (
|
||||
route: string,
|
||||
listener: (...args: any[]) => any
|
||||
) => {
|
||||
return ipcMain.handle(route, async (event, ...args: any[]) => {
|
||||
) =>
|
||||
ipcMain.handle(route, async (_event, ...args: any[]) => {
|
||||
return listener(...args)
|
||||
})
|
||||
}
|
||||
|
||||
const handler = new RequestHandler(
|
||||
ipcWrapper,
|
||||
(channel: string, args: any) => {
|
||||
return WindowManager.instance.currentWindow?.webContents.send(
|
||||
channel,
|
||||
args
|
||||
)
|
||||
}
|
||||
(channel: string, args: any) =>
|
||||
windowManager.mainWindow?.webContents.send(channel, args)
|
||||
)
|
||||
handler.handle()
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { app, ipcMain, dialog, shell } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { WindowManager } from '../managers/window'
|
||||
import { windowManager } from '../managers/window'
|
||||
import {
|
||||
ModuleManager,
|
||||
getJanDataFolderPath,
|
||||
getJanExtensionsPath,
|
||||
init,
|
||||
} from '@janhq/core/node'
|
||||
import { NativeRoute } from '@janhq/core'
|
||||
import { AppEvent, NativeRoute } from '@janhq/core'
|
||||
|
||||
export function handleAppIPCs() {
|
||||
/**
|
||||
@ -62,12 +62,12 @@ export function handleAppIPCs() {
|
||||
// Path to install extension to
|
||||
extensionsPath: getJanExtensionsPath(),
|
||||
})
|
||||
WindowManager.instance.currentWindow?.reload()
|
||||
windowManager.mainWindow?.reload()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(NativeRoute.selectDirectory, async () => {
|
||||
const mainWindow = WindowManager.instance.currentWindow
|
||||
const mainWindow = windowManager.mainWindow
|
||||
if (!mainWindow) {
|
||||
console.error('No main window found')
|
||||
return
|
||||
@ -85,7 +85,7 @@ export function handleAppIPCs() {
|
||||
})
|
||||
|
||||
ipcMain.handle(NativeRoute.selectModelFiles, async () => {
|
||||
const mainWindow = WindowManager.instance.currentWindow
|
||||
const mainWindow = windowManager.mainWindow
|
||||
if (!mainWindow) {
|
||||
console.error('No main window found')
|
||||
return
|
||||
@ -101,4 +101,35 @@ export function handleAppIPCs() {
|
||||
|
||||
return filePaths
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
NativeRoute.hideQuickAskWindow,
|
||||
async (): Promise<void> => windowManager.hideQuickAskWindow()
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
NativeRoute.sendQuickAskInput,
|
||||
async (_event, input: string): Promise<void> => {
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onUserSubmitQuickAsk,
|
||||
input
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
NativeRoute.hideMainWindow,
|
||||
async (): Promise<void> => windowManager.hideMainWindow()
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
NativeRoute.showMainWindow,
|
||||
async (): Promise<void> => windowManager.showMainWindow()
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
NativeRoute.quickAskSizeUpdated,
|
||||
async (_event, heightOffset: number): Promise<void> =>
|
||||
windowManager.expandQuickAskWindow(heightOffset)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import { app, dialog } from 'electron'
|
||||
import { WindowManager } from './../managers/window'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { windowManager } from './../managers/window'
|
||||
import {
|
||||
ProgressInfo,
|
||||
UpdateDownloadedEvent,
|
||||
UpdateInfo,
|
||||
autoUpdater,
|
||||
} from 'electron-updater'
|
||||
import { AppEvent } from '@janhq/core'
|
||||
|
||||
export let waitingToInstallVersion: string | undefined = undefined
|
||||
|
||||
export function handleAppUpdates() {
|
||||
/* Should not check for update during development */
|
||||
if (!app.isPackaged) {
|
||||
return
|
||||
}
|
||||
/* New Update Available */
|
||||
autoUpdater.on('update-available', async (_info: any) => {
|
||||
autoUpdater.on('update-available', async (_info: UpdateInfo) => {
|
||||
const action = await dialog.showMessageBox({
|
||||
title: 'Update Available',
|
||||
message: 'Would you like to download and install it now?',
|
||||
@ -19,8 +26,8 @@ export function handleAppUpdates() {
|
||||
})
|
||||
|
||||
/* App Update Completion Message */
|
||||
autoUpdater.on('update-downloaded', async (_info: any) => {
|
||||
WindowManager.instance.currentWindow?.webContents.send(
|
||||
autoUpdater.on('update-downloaded', async (_info: UpdateDownloadedEvent) => {
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onAppUpdateDownloadSuccess,
|
||||
{}
|
||||
)
|
||||
@ -29,28 +36,30 @@ export function handleAppUpdates() {
|
||||
buttons: ['Restart', 'Later'],
|
||||
})
|
||||
if (action.response === 0) {
|
||||
waitingToInstallVersion = _info?.version
|
||||
autoUpdater.quitAndInstall()
|
||||
}
|
||||
})
|
||||
|
||||
/* App Update Error */
|
||||
autoUpdater.on('error', (info: any) => {
|
||||
WindowManager.instance.currentWindow?.webContents.send(
|
||||
autoUpdater.on('error', (info: Error) => {
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onAppUpdateDownloadError,
|
||||
info
|
||||
{ failedToInstallVersion: waitingToInstallVersion, info }
|
||||
)
|
||||
})
|
||||
|
||||
/* App Update Progress */
|
||||
autoUpdater.on('download-progress', (progress: any) => {
|
||||
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
|
||||
console.debug('app update progress: ', progress.percent)
|
||||
WindowManager.instance.currentWindow?.webContents.send(
|
||||
windowManager.mainWindow?.webContents.send(
|
||||
AppEvent.onAppUpdateDownloadUpdate,
|
||||
{
|
||||
percent: progress.percent,
|
||||
...progress,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
autoUpdater.autoDownload = false
|
||||
autoUpdater.autoInstallOnAppQuit = true
|
||||
if (process.env.CI !== 'e2e') {
|
||||
|
||||
BIN
electron/icons/icon-tray.png
Normal file
BIN
electron/icons/icon-tray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
electron/icons/icon-tray@2x.png
Normal file
BIN
electron/icons/icon-tray@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@ -1,9 +1,10 @@
|
||||
import { app, BrowserWindow, shell } from 'electron'
|
||||
import { app, BrowserWindow, Menu, Tray } from 'electron'
|
||||
|
||||
import { join } from 'path'
|
||||
/**
|
||||
* Managers
|
||||
**/
|
||||
import { WindowManager } from './managers/window'
|
||||
import { windowManager } from './managers/window'
|
||||
import { log } from '@janhq/core/node'
|
||||
|
||||
/**
|
||||
@ -25,6 +26,19 @@ import { setupCore } from './utils/setup'
|
||||
import { setupReactDevTool } from './utils/dev'
|
||||
import { cleanLogs } from './utils/log'
|
||||
|
||||
import { registerShortcut } from './utils/selectedText'
|
||||
import { createSystemTray } from './utils/tray'
|
||||
|
||||
const preloadPath = join(__dirname, 'preload.js')
|
||||
const rendererPath = join(__dirname, '..', 'renderer')
|
||||
const quickAskPath = join(rendererPath, 'search.html')
|
||||
const mainPath = join(rendererPath, 'index.html')
|
||||
|
||||
const mainUrl = 'http://localhost:3000'
|
||||
const quickAskUrl = `${mainUrl}/search`
|
||||
|
||||
const quickAskHotKey = 'CommandOrControl+J'
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(setupReactDevTool)
|
||||
@ -35,7 +49,17 @@ app
|
||||
.then(setupMenu)
|
||||
.then(handleIPCs)
|
||||
.then(handleAppUpdates)
|
||||
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
|
||||
.then(createMainWindow)
|
||||
.then(() => {
|
||||
if (!app.isPackaged) {
|
||||
windowManager.mainWindow?.webContents.openDevTools()
|
||||
}
|
||||
})
|
||||
.then(() => process.env.CI !== 'e2e' && createSystemTray())
|
||||
.then(() => {
|
||||
log(`Version: ${app.getVersion()}`)
|
||||
})
|
||||
.then(() => {
|
||||
app.on('activate', () => {
|
||||
if (!BrowserWindow.getAllWindows().length) {
|
||||
@ -45,45 +69,39 @@ app
|
||||
})
|
||||
.then(() => cleanLogs())
|
||||
|
||||
app.once('window-all-closed', () => {
|
||||
cleanUpAndQuit()
|
||||
app.on('ready', () => {
|
||||
registerGlobalShortcuts()
|
||||
})
|
||||
|
||||
app.once('quit', () => {
|
||||
cleanUpAndQuit()
|
||||
})
|
||||
|
||||
function createQuickAskWindow() {
|
||||
const startUrl = app.isPackaged ? `file://${quickAskPath}` : quickAskUrl
|
||||
windowManager.createQuickAskWindow(preloadPath, startUrl)
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
/* Create main window */
|
||||
const mainWindow = WindowManager.instance.createWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
webSecurity: false,
|
||||
},
|
||||
const startUrl = app.isPackaged ? `file://${mainPath}` : mainUrl
|
||||
windowManager.createMainWindow(preloadPath, startUrl)
|
||||
}
|
||||
|
||||
function registerGlobalShortcuts() {
|
||||
const ret = registerShortcut(quickAskHotKey, (selectedText: string) => {
|
||||
if (!windowManager.isQuickAskWindowVisible()) {
|
||||
windowManager.showQuickAskWindow()
|
||||
windowManager.sendQuickAskSelectedText(selectedText)
|
||||
} else {
|
||||
windowManager.hideQuickAskWindow()
|
||||
}
|
||||
})
|
||||
|
||||
const startURL = app.isPackaged
|
||||
? `file://${join(__dirname, '..', 'renderer', 'index.html')}`
|
||||
: 'http://localhost:3000'
|
||||
|
||||
/* Load frontend app to the window */
|
||||
mainWindow.loadURL(startURL)
|
||||
|
||||
mainWindow.once('ready-to-show', () => mainWindow?.show())
|
||||
mainWindow.on('closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
/* Open external links in the default browser */
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
/* Enable dev tools for development */
|
||||
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
||||
log(`Version: ${app.getVersion()}`)
|
||||
if (!ret) {
|
||||
console.error('Global shortcut registration failed')
|
||||
} else {
|
||||
console.log('Global shortcut registered successfully')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
16
electron/managers/mainWindowConfig.ts
Normal file
16
electron/managers/mainWindowConfig.ts
Normal file
@ -0,0 +1,16 @@
|
||||
const DEFAULT_WIDTH = 1200
|
||||
const DEFAULT_HEIGHT = 800
|
||||
|
||||
export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||
width: DEFAULT_WIDTH,
|
||||
minWidth: DEFAULT_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
skipTaskbar: true,
|
||||
show: true,
|
||||
trafficLightPosition: {
|
||||
x: 10,
|
||||
y: 15,
|
||||
},
|
||||
titleBarStyle: 'hiddenInset',
|
||||
vibrancy: 'sidebar',
|
||||
}
|
||||
22
electron/managers/quickAskWindowConfig.ts
Normal file
22
electron/managers/quickAskWindowConfig.ts
Normal file
@ -0,0 +1,22 @@
|
||||
const DEFAULT_WIDTH = 556
|
||||
|
||||
const DEFAULT_HEIGHT = 60
|
||||
|
||||
export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||
width: DEFAULT_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
skipTaskbar: true,
|
||||
acceptFirstMouse: true,
|
||||
hasShadow: true,
|
||||
alwaysOnTop: true,
|
||||
show: false,
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
center: true,
|
||||
movable: false,
|
||||
maximizable: false,
|
||||
focusable: true,
|
||||
transparent: true,
|
||||
frame: false,
|
||||
type: 'panel',
|
||||
}
|
||||
@ -1,37 +1,122 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { BrowserWindow, app, shell } from 'electron'
|
||||
import { quickAskWindowConfig } from './quickAskWindowConfig'
|
||||
import { AppEvent } from '@janhq/core'
|
||||
import { mainWindowConfig } from './mainWindowConfig'
|
||||
|
||||
/**
|
||||
* Manages the current window instance.
|
||||
*/
|
||||
export class WindowManager {
|
||||
public static instance: WindowManager = new WindowManager()
|
||||
public currentWindow?: BrowserWindow
|
||||
|
||||
constructor() {
|
||||
if (WindowManager.instance) {
|
||||
return WindowManager.instance
|
||||
}
|
||||
}
|
||||
// TODO: refactor this
|
||||
let isAppQuitting = false
|
||||
class WindowManager {
|
||||
public mainWindow?: BrowserWindow
|
||||
private _quickAskWindow: BrowserWindow | undefined = undefined
|
||||
private _quickAskWindowVisible = false
|
||||
private _mainWindowVisible = false
|
||||
|
||||
/**
|
||||
* Creates a new window instance.
|
||||
* @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with.
|
||||
* @returns The created window instance.
|
||||
*/
|
||||
createWindow(options?: Electron.BrowserWindowConstructorOptions | undefined) {
|
||||
this.currentWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
minWidth: 1200,
|
||||
height: 800,
|
||||
show: true,
|
||||
trafficLightPosition: {
|
||||
x: 10,
|
||||
y: 15,
|
||||
createMainWindow(preloadPath: string, startUrl: string) {
|
||||
this.mainWindow = new BrowserWindow({
|
||||
...mainWindowConfig,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: preloadPath,
|
||||
webSecurity: false,
|
||||
},
|
||||
titleBarStyle: 'hiddenInset',
|
||||
vibrancy: 'sidebar',
|
||||
...options,
|
||||
})
|
||||
return this.currentWindow
|
||||
|
||||
/* Load frontend app to the window */
|
||||
this.mainWindow.loadURL(startUrl)
|
||||
|
||||
/* Open external links in the default browser */
|
||||
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
app.on('before-quit', function () {
|
||||
isAppQuitting = true
|
||||
})
|
||||
|
||||
windowManager.mainWindow?.on('close', function (evt) {
|
||||
if (!isAppQuitting) {
|
||||
evt.preventDefault()
|
||||
windowManager.hideMainWindow()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createQuickAskWindow(preloadPath: string, startUrl: string): void {
|
||||
this._quickAskWindow = new BrowserWindow({
|
||||
...quickAskWindowConfig,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: preloadPath,
|
||||
webSecurity: false,
|
||||
},
|
||||
})
|
||||
|
||||
this._quickAskWindow.loadURL(startUrl)
|
||||
this._quickAskWindow.on('blur', () => {
|
||||
this.hideQuickAskWindow()
|
||||
})
|
||||
}
|
||||
|
||||
isMainWindowVisible(): boolean {
|
||||
return this._mainWindowVisible
|
||||
}
|
||||
|
||||
hideMainWindow(): void {
|
||||
this.mainWindow?.hide()
|
||||
this._mainWindowVisible = false
|
||||
// Only macos
|
||||
if (process.platform === 'darwin') app.dock.hide()
|
||||
}
|
||||
|
||||
showMainWindow(): void {
|
||||
this.mainWindow?.show()
|
||||
this._mainWindowVisible = true
|
||||
// Only macos
|
||||
if (process.platform === 'darwin') app.dock.show()
|
||||
}
|
||||
|
||||
hideQuickAskWindow(): void {
|
||||
this._quickAskWindow?.hide()
|
||||
this._quickAskWindowVisible = false
|
||||
}
|
||||
|
||||
showQuickAskWindow(): void {
|
||||
this._quickAskWindow?.show()
|
||||
this._quickAskWindowVisible = true
|
||||
}
|
||||
|
||||
isQuickAskWindowVisible(): boolean {
|
||||
return this._quickAskWindowVisible
|
||||
}
|
||||
|
||||
expandQuickAskWindow(heightOffset: number): void {
|
||||
const width = quickAskWindowConfig.width!
|
||||
const height = quickAskWindowConfig.height! + heightOffset
|
||||
this._quickAskWindow?.setSize(width, height, true)
|
||||
}
|
||||
|
||||
sendQuickAskSelectedText(selectedText: string): void {
|
||||
this._quickAskWindow?.webContents.send(
|
||||
AppEvent.onSelectedText,
|
||||
selectedText
|
||||
)
|
||||
}
|
||||
|
||||
cleanUp(): void {
|
||||
this.mainWindow?.destroy()
|
||||
this._quickAskWindow?.destroy()
|
||||
this._quickAskWindowVisible = false
|
||||
this._mainWindowVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
export const windowManager = new WindowManager()
|
||||
|
||||
@ -16,13 +16,15 @@
|
||||
"pre-install",
|
||||
"models/**/*",
|
||||
"docs/**/*",
|
||||
"scripts/**/*"
|
||||
"scripts/**/*",
|
||||
"icons/**/*"
|
||||
],
|
||||
"asarUnpack": [
|
||||
"pre-install",
|
||||
"models",
|
||||
"docs",
|
||||
"scripts"
|
||||
"scripts",
|
||||
"icons"
|
||||
],
|
||||
"publish": [
|
||||
{
|
||||
@ -81,7 +83,6 @@
|
||||
"@janhq/core": "link:./core",
|
||||
"@janhq/server": "link:./server",
|
||||
"@npmcli/arborist": "^7.1.0",
|
||||
"@uiball/loaders": "^1.3.0",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"fs-extra": "^11.2.0",
|
||||
@ -90,7 +91,7 @@
|
||||
"request": "^2.88.2",
|
||||
"request-progress": "^3.0.0",
|
||||
"ulid": "^2.3.0",
|
||||
"use-debounce": "^9.0.4"
|
||||
"@nut-tree/nut-js": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^2.1.0",
|
||||
@ -101,13 +102,15 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||
"@typescript-eslint/parser": "^6.7.3",
|
||||
"electron": "28.0.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-builder-squirrel-windows": "^24.13.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-playwright-helpers": "^1.6.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"run-script-os": "^1.1.6",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"installConfig": {
|
||||
"hoistingLimits": "workspaces"
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { ModuleManager } from '@janhq/core/node'
|
||||
import { WindowManager } from './../managers/window'
|
||||
import { windowManager } from './../managers/window'
|
||||
import { dispose } from './disposable'
|
||||
import { app } from 'electron'
|
||||
|
||||
export function cleanUpAndQuit() {
|
||||
if (!ModuleManager.instance.cleaningResource) {
|
||||
ModuleManager.instance.cleaningResource = true
|
||||
WindowManager.instance.currentWindow?.destroy()
|
||||
windowManager.cleanUp()
|
||||
dispose(ModuleManager.instance.requiredModules)
|
||||
ModuleManager.instance.clearImportedModules()
|
||||
app.quit()
|
||||
|
||||
44
electron/utils/selectedText.ts
Normal file
44
electron/utils/selectedText.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { clipboard, globalShortcut } from 'electron'
|
||||
import { keyboard, Key } from '@nut-tree/nut-js'
|
||||
|
||||
/**
|
||||
* Gets selected text by synthesizing the keyboard shortcut
|
||||
* "CommandOrControl+c" then reading text from the clipboard
|
||||
*/
|
||||
export const getSelectedText = async () => {
|
||||
const currentClipboardContent = clipboard.readText() // preserve clipboard content
|
||||
clipboard.clear()
|
||||
const hotkeys: Key[] = [
|
||||
process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl,
|
||||
Key.C,
|
||||
]
|
||||
await keyboard.pressKey(...hotkeys)
|
||||
await keyboard.releaseKey(...hotkeys)
|
||||
await new Promise((resolve) => setTimeout(resolve, 200)) // add a delay before checking clipboard
|
||||
const selectedText = clipboard.readText()
|
||||
clipboard.writeText(currentClipboardContent)
|
||||
return selectedText
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a global shortcut of `accelerator`. The `callback` is called
|
||||
* with the selected text when the registered shorcut is pressed by the user
|
||||
*
|
||||
* Returns `true` if the shortcut was registered successfully
|
||||
*/
|
||||
export const registerShortcut = (
|
||||
accelerator: Electron.Accelerator,
|
||||
callback: (selectedText: string) => void
|
||||
) => {
|
||||
return globalShortcut.register(accelerator, async () => {
|
||||
callback(await getSelectedText())
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a global shortcut of `accelerator` and
|
||||
* is equivalent to electron.globalShortcut.unregister
|
||||
*/
|
||||
export const unregisterShortcut = (accelerator: Electron.Accelerator) => {
|
||||
globalShortcut.unregister(accelerator)
|
||||
}
|
||||
24
electron/utils/tray.ts
Normal file
24
electron/utils/tray.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { join } from 'path'
|
||||
import { Tray, app, Menu } from 'electron'
|
||||
import { windowManager } from '../managers/window'
|
||||
|
||||
export const createSystemTray = () => {
|
||||
const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png')
|
||||
const tray = new Tray(iconPath)
|
||||
tray.setToolTip(app.getName())
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Open Jan',
|
||||
type: 'normal',
|
||||
click: () => windowManager.showMainWindow(),
|
||||
},
|
||||
{
|
||||
label: 'Open Quick Ask',
|
||||
type: 'normal',
|
||||
click: () => windowManager.showQuickAskWindow(),
|
||||
},
|
||||
{ label: 'Quit', type: 'normal', click: () => app.quit() },
|
||||
])
|
||||
tray.setContextMenu(contextMenu)
|
||||
}
|
||||
@ -26,7 +26,7 @@
|
||||
"rollup-plugin-define": "^1.0.1",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.3.3",
|
||||
"run-script-os": "^1.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.3.3",
|
||||
"@types/os-utils": "^0.0.4",
|
||||
"@rollup/plugin-replace": "^5.0.5"
|
||||
},
|
||||
|
||||
@ -39,8 +39,8 @@
|
||||
"@types/tcp-port-used": "^1.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||
"@typescript-eslint/parser": "^6.7.3",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +52,6 @@
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"terser": "^5.24.0",
|
||||
"tsup": "^7.2.0",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
47
web/app/search/SelectedText.tsx
Normal file
47
web/app/search/SelectedText.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
import { useAtom } from 'jotai'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
import { selectedTextAtom } from '@/containers/Providers/Jotai'
|
||||
|
||||
const SelectedText = ({ onCleared }: { onCleared?: () => void }) => {
|
||||
const [text, setText] = useAtom(selectedTextAtom)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (text.trim().length === 0) {
|
||||
window.core?.api?.quickAskSizeUpdated(0)
|
||||
} else {
|
||||
window.core?.api?.quickAskSizeUpdated(
|
||||
(containerRef.current?.offsetHeight ?? 0) + 14
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const onClearClicked = useCallback(() => {
|
||||
setText('')
|
||||
onCleared?.()
|
||||
}, [setText, onCleared])
|
||||
|
||||
const shouldShowSelectedText = text.trim().length > 0
|
||||
|
||||
return shouldShowSelectedText ? (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative rounded-lg border-[1px] border-[#0000000F] bg-[#0000000A] p-[10px]"
|
||||
>
|
||||
<div
|
||||
className="absolute right-1 top-1 flex h-6 w-6 items-center justify-center rounded-full border-[1px] border-[#0000000F] bg-white drop-shadow"
|
||||
onClick={onClearClicked}
|
||||
>
|
||||
<X size={16} />
|
||||
</div>
|
||||
<p className="font-semibold text-[#00000099]">{text}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectedText
|
||||
86
web/app/search/UserInput.tsx
Normal file
86
web/app/search/UserInput.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
|
||||
import { Button } from '@janhq/uikit'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { Send } from 'lucide-react'
|
||||
|
||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||
|
||||
import { selectedTextAtom } from '@/containers/Providers/Jotai'
|
||||
|
||||
import SelectedText from './SelectedText'
|
||||
|
||||
const UserInput: React.FC = () => {
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const selectedText = useAtomValue(selectedTextAtom)
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
window.core?.api?.hideQuickAskWindow()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleChange = (
|
||||
event:
|
||||
| React.ChangeEvent<HTMLInputElement>
|
||||
| React.ChangeEvent<HTMLTextAreaElement>
|
||||
) => {
|
||||
const { value } = event.target
|
||||
setInputValue(value)
|
||||
}
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (inputValue.trim() !== '') {
|
||||
const fullText = `${inputValue} ${selectedText}`.trim()
|
||||
window.core?.api?.sendQuickAskInput(fullText)
|
||||
setInputValue('')
|
||||
window.core?.api?.hideQuickAskWindow()
|
||||
window.core?.api?.showMainWindow()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-3 p-3">
|
||||
<form
|
||||
ref={formRef}
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div className="flex h-full w-full items-center gap-4">
|
||||
<LogoMark width={28} height={28} className="mx-auto" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="flex-1 bg-transparent font-bold text-black focus:outline-none"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
placeholder="Ask me anything"
|
||||
/>
|
||||
<Button onClick={onSubmit}>
|
||||
<Send size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<SelectedText onCleared={() => inputRef?.current?.focus()} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserInput
|
||||
13
web/app/search/page.tsx
Normal file
13
web/app/search/page.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import UserInput from './UserInput'
|
||||
|
||||
const Search: React.FC = () => {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-white">
|
||||
<UserInput />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Search
|
||||
69
web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx
Normal file
69
web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
|
||||
import {
|
||||
Modal,
|
||||
ModalPortal,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalTitle,
|
||||
ModalFooter,
|
||||
ModalClose,
|
||||
Button,
|
||||
} from '@janhq/uikit'
|
||||
import { Share2Icon } from '@radix-ui/react-icons'
|
||||
import { useAtom } from 'jotai'
|
||||
|
||||
import { updateVersionError } from '@/containers/Providers/Jotai'
|
||||
|
||||
const UpdatedFailedModal = () => {
|
||||
const [error, setError] = useAtom(updateVersionError)
|
||||
|
||||
return (
|
||||
<Modal open={!!error} onOpenChange={() => setError(undefined)}>
|
||||
<ModalPortal />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<ModalTitle>Unable to Install Update</ModalTitle>
|
||||
</ModalHeader>
|
||||
<p className="text-muted-foreground">
|
||||
An error occurred while installing Jan{' '}
|
||||
<span className="font-medium text-foreground">{error}</span>. We
|
||||
appreciate your help with{' '}
|
||||
<a
|
||||
href="https://github.com/janhq/jan#download"
|
||||
target="_blank"
|
||||
className="font-medium text-foreground"
|
||||
>
|
||||
manual downloading and installation.
|
||||
</a>
|
||||
</p>
|
||||
<ModalFooter>
|
||||
<div className="flex gap-x-2">
|
||||
<ModalClose asChild onClick={() => setError(undefined)}>
|
||||
<Button themes="outline">Remind me later</Button>
|
||||
</ModalClose>
|
||||
<ModalClose
|
||||
asChild
|
||||
onClick={() => {
|
||||
window.open('https://github.com/janhq/jan#download', '_blank')
|
||||
setError(undefined)
|
||||
}}
|
||||
>
|
||||
<Button themes="primary" autoFocus>
|
||||
Download now{' '}
|
||||
<Share2Icon
|
||||
width={16}
|
||||
height={16}
|
||||
className="ml-2"
|
||||
color="white"
|
||||
/>
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdatedFailedModal
|
||||
@ -17,6 +17,7 @@ import { appDownloadProgress } from '@/containers/Providers/Jotai'
|
||||
|
||||
import ImportingModelState from './ImportingModelState'
|
||||
import SystemMonitor from './SystemMonitor'
|
||||
import UpdatedFailedModal from './UpdateFailedModal'
|
||||
|
||||
const menuLinks = [
|
||||
{
|
||||
@ -44,6 +45,7 @@ const BottomBar = () => {
|
||||
</div>
|
||||
<ImportingModelState />
|
||||
<DownloadingState />
|
||||
<UpdatedFailedModal />
|
||||
</div>
|
||||
<div className="flex items-center gap-x-3">
|
||||
<SystemMonitor />
|
||||
|
||||
@ -1,26 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Fragment, PropsWithChildren, useEffect } from 'react'
|
||||
|
||||
import { AppUpdateInfo } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { appDownloadProgress } from './Jotai'
|
||||
import { appDownloadProgress, updateVersionError } from './Jotai'
|
||||
|
||||
const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
||||
const setProgress = useSetAtom(appDownloadProgress)
|
||||
const setUpdateVersionError = useSetAtom(updateVersionError)
|
||||
|
||||
useEffect(() => {
|
||||
if (window && window.electronAPI) {
|
||||
window.electronAPI.onAppUpdateDownloadUpdate(
|
||||
(_event: string, progress: any) => {
|
||||
setProgress(progress.percent)
|
||||
console.debug('app update progress:', progress.percent)
|
||||
(_event: string, appUpdateInfo: AppUpdateInfo) => {
|
||||
setProgress(appUpdateInfo.percent)
|
||||
console.debug('app update progress:', appUpdateInfo.percent)
|
||||
}
|
||||
)
|
||||
|
||||
window.electronAPI.onAppUpdateDownloadError(
|
||||
(_event: string, callback: any) => {
|
||||
console.error('Download error', callback)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(_event: string, error: any) => {
|
||||
console.error('Download error: ', error)
|
||||
setProgress(-1)
|
||||
|
||||
// Can not install update
|
||||
// Prompt user to download the update manually
|
||||
setUpdateVersionError(error.failedToInstallVersion)
|
||||
}
|
||||
)
|
||||
|
||||
@ -28,8 +34,7 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
||||
setProgress(-1)
|
||||
})
|
||||
}
|
||||
return () => {}
|
||||
}, [setProgress])
|
||||
}, [setProgress, setUpdateVersionError])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
}
|
||||
|
||||
17
web/containers/Providers/ClipboardListener.tsx
Normal file
17
web/containers/Providers/ClipboardListener.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { Fragment, PropsWithChildren } from 'react'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { selectedTextAtom } from './Jotai'
|
||||
|
||||
const ClipboardListener = ({ children }: PropsWithChildren) => {
|
||||
const setSelectedText = useSetAtom(selectedTextAtom)
|
||||
|
||||
window?.electronAPI?.onSelectedText((_event: string, text: string) => {
|
||||
setSelectedText(text)
|
||||
})
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
}
|
||||
|
||||
export default ClipboardListener
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { Fragment, ReactNode, useEffect } from 'react'
|
||||
|
||||
import { AppConfiguration } from '@janhq/core/.'
|
||||
import { AppConfiguration } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import useAssistants from '@/hooks/useAssistants'
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Fragment, ReactNode, useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
import {
|
||||
@ -15,6 +14,7 @@ import {
|
||||
MessageRequestType,
|
||||
ModelEvent,
|
||||
Thread,
|
||||
ModelInitFailed,
|
||||
} from '@janhq/core'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { ulid } from 'ulid'
|
||||
@ -113,15 +113,14 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
}, [setActiveModel, setStateModel])
|
||||
|
||||
const onModelInitFailed = useCallback(
|
||||
(res: any) => {
|
||||
const errorMessage = res?.error ?? res
|
||||
console.error('Failed to load model: ', errorMessage)
|
||||
(res: ModelInitFailed) => {
|
||||
console.error('Failed to load model: ', res.error.message)
|
||||
setStateModel(() => ({
|
||||
state: 'start',
|
||||
loading: false,
|
||||
model: res.modelId,
|
||||
model: res.id,
|
||||
}))
|
||||
setLoadModelError(errorMessage)
|
||||
setLoadModelError(res.error.message)
|
||||
setQueuedMessage(false)
|
||||
},
|
||||
[setStateModel, setQueuedMessage, setLoadModelError]
|
||||
@ -245,7 +244,7 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
|
||||
if (!threadMessages || threadMessages.length === 0) return
|
||||
|
||||
const summarizeFirstPrompt = `Summarize this text "${threadMessages[0].content[0].text.value}" for a conversation title in less than 10 words`
|
||||
const summarizeFirstPrompt = `Summarize in a 5-word Title. Give the title only. "${threadMessages[0].content[0].text.value}"`
|
||||
// Prompt: Given this query from user {query}, return to me the summary in 5 words as the title
|
||||
const msgId = ulid()
|
||||
const messages: ChatCompletionMessage[] = [
|
||||
|
||||
@ -8,9 +8,11 @@ import { useSetAtom } from 'jotai'
|
||||
import { setDownloadStateAtom } from '@/hooks/useDownloadState'
|
||||
|
||||
import AppUpdateListener from './AppUpdateListener'
|
||||
import ClipboardListener from './ClipboardListener'
|
||||
import EventHandler from './EventHandler'
|
||||
|
||||
import ModelImportListener from './ModelImportListener'
|
||||
import QuickAskListener from './QuickAskListener'
|
||||
|
||||
const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||
@ -55,9 +57,13 @@ const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
||||
|
||||
return (
|
||||
<AppUpdateListener>
|
||||
<ClipboardListener>
|
||||
<ModelImportListener>
|
||||
<QuickAskListener>
|
||||
<EventHandler>{children}</EventHandler>
|
||||
</QuickAskListener>
|
||||
</ModelImportListener>
|
||||
</ClipboardListener>
|
||||
</AppUpdateListener>
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,8 +12,11 @@ export const editPromptAtom = atom<string>('')
|
||||
export const currentPromptAtom = atom<string>('')
|
||||
export const fileUploadAtom = atom<FileInfo[]>([])
|
||||
export const appDownloadProgress = atom<number>(-1)
|
||||
export const updateVersionError = atom<string | undefined>(undefined)
|
||||
export const searchAtom = atom<string>('')
|
||||
|
||||
export const selectedTextAtom = atom('')
|
||||
|
||||
export default function JotaiWrapper({ children }: Props) {
|
||||
return <Provider>{children}</Provider>
|
||||
}
|
||||
|
||||
@ -24,6 +24,10 @@ export default function KeyListener({ children }: Props) {
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
window.core?.api?.hideMainWindow()
|
||||
}
|
||||
|
||||
const prefixKey = isMac ? e.metaKey : e.ctrlKey
|
||||
|
||||
if (e.key === 'b' && prefixKey) {
|
||||
|
||||
39
web/containers/Providers/QuickAskListener.tsx
Normal file
39
web/containers/Providers/QuickAskListener.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Fragment, ReactNode, useRef } from 'react'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { MainViewState } from '@/constants/screens'
|
||||
|
||||
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
||||
|
||||
import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
||||
|
||||
import { showLeftSideBarAtom } from './KeyListener'
|
||||
|
||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const QuickAskListener: React.FC<Props> = ({ children }) => {
|
||||
const { sendChatMessage } = useSendChatMessage()
|
||||
const setShowRightSideBar = useSetAtom(showRightSideBarAtom)
|
||||
const setShowLeftSideBar = useSetAtom(showLeftSideBarAtom)
|
||||
const setMainState = useSetAtom(mainViewStateAtom)
|
||||
|
||||
const previousMessage = useRef('')
|
||||
|
||||
window.electronAPI.onUserSubmitQuickAsk((_event: string, input: string) => {
|
||||
if (previousMessage.current === input) return
|
||||
setMainState(MainViewState.Thread)
|
||||
setShowRightSideBar(false)
|
||||
setShowLeftSideBar(false)
|
||||
sendChatMessage(input)
|
||||
previousMessage.current = input
|
||||
})
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
}
|
||||
|
||||
export default QuickAskListener
|
||||
@ -1,4 +1,4 @@
|
||||
import { Assistant } from '@janhq/core/.'
|
||||
import { Assistant } from '@janhq/core'
|
||||
import { atom } from 'jotai'
|
||||
|
||||
export const assistantsAtom = atom<Assistant[]>([])
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { events, Model, ModelEvent } from '@janhq/core'
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
|
||||
@ -24,6 +26,12 @@ export function useActiveModel() {
|
||||
const downloadedModels = useAtomValue(downloadedModelsAtom)
|
||||
const setLoadModelError = useSetAtom(loadModelErrorAtom)
|
||||
|
||||
const downloadedModelsRef = useRef<Model[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
downloadedModelsRef.current = downloadedModels
|
||||
}, [downloadedModels])
|
||||
|
||||
const startModel = async (modelId: string) => {
|
||||
if (
|
||||
(activeModel && activeModel.id === modelId) ||
|
||||
@ -39,7 +47,7 @@ export function useActiveModel() {
|
||||
|
||||
setStateModel({ state: 'start', loading: true, model: modelId })
|
||||
|
||||
let model = downloadedModels.find((e) => e.id === modelId)
|
||||
let model = downloadedModelsRef?.current.find((e) => e.id === modelId)
|
||||
|
||||
if (!model) {
|
||||
toaster({
|
||||
|
||||
@ -63,9 +63,8 @@ export default function useSendChatMessage() {
|
||||
const setEditPrompt = useSetAtom(editPromptAtom)
|
||||
|
||||
const currentMessages = useAtomValue(getCurrentChatMessagesAtom)
|
||||
const { activeModel } = useActiveModel()
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
const { startModel } = useActiveModel()
|
||||
const { activeModel, startModel } = useActiveModel()
|
||||
const setQueuedMessage = useSetAtom(queuedMessageAtom)
|
||||
const loadModelFailed = useAtomValue(loadModelErrorAtom)
|
||||
|
||||
@ -78,6 +77,7 @@ export default function useSendChatMessage() {
|
||||
const setReloadModel = useSetAtom(reloadModelAtom)
|
||||
const [fileUpload, setFileUpload] = useAtom(fileUploadAtom)
|
||||
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
|
||||
const activeThreadRef = useRef<Thread | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
modelRef.current = activeModel
|
||||
@ -87,15 +87,19 @@ export default function useSendChatMessage() {
|
||||
loadModelFailedRef.current = loadModelFailed
|
||||
}, [loadModelFailed])
|
||||
|
||||
useEffect(() => {
|
||||
activeThreadRef.current = activeThread
|
||||
}, [activeThread])
|
||||
|
||||
const resendChatMessage = async (currentMessage: ThreadMessage) => {
|
||||
if (!activeThread) {
|
||||
if (!activeThreadRef.current) {
|
||||
console.error('No active thread')
|
||||
return
|
||||
}
|
||||
setIsGeneratingResponse(true)
|
||||
updateThreadWaiting(activeThread.id, true)
|
||||
updateThreadWaiting(activeThreadRef.current.id, true)
|
||||
const messages: ChatCompletionMessage[] = [
|
||||
activeThread.assistants[0]?.instructions,
|
||||
activeThreadRef.current.assistants[0]?.instructions,
|
||||
]
|
||||
.filter((e) => e && e.trim() !== '')
|
||||
.map<ChatCompletionMessage>((instructions) => {
|
||||
@ -123,13 +127,14 @@ export default function useSendChatMessage() {
|
||||
id: ulid(),
|
||||
type: MessageRequestType.Thread,
|
||||
messages: messages,
|
||||
threadId: activeThread.id,
|
||||
model: activeThread.assistants[0].model ?? selectedModel,
|
||||
threadId: activeThreadRef.current.id,
|
||||
model: activeThreadRef.current.assistants[0].model ?? selectedModel,
|
||||
}
|
||||
|
||||
const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id
|
||||
const modelId =
|
||||
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id
|
||||
|
||||
if (activeModel?.id !== modelId) {
|
||||
if (modelRef.current?.id !== modelId) {
|
||||
setQueuedMessage(true)
|
||||
startModel(modelId)
|
||||
await waitForModelStarting(modelId)
|
||||
@ -139,11 +144,11 @@ export default function useSendChatMessage() {
|
||||
if (currentMessage.role !== ChatCompletionRole.User) {
|
||||
// Delete last response before regenerating
|
||||
deleteMessage(currentMessage.id ?? '')
|
||||
if (activeThread) {
|
||||
if (activeThreadRef.current) {
|
||||
await extensionManager
|
||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||
?.writeMessages(
|
||||
activeThread.id,
|
||||
activeThreadRef.current.id,
|
||||
currentMessages.filter((msg) => msg.id !== currentMessage.id)
|
||||
)
|
||||
}
|
||||
@ -154,7 +159,7 @@ export default function useSendChatMessage() {
|
||||
const sendChatMessage = async (message: string) => {
|
||||
if (!message || message.trim().length === 0) return
|
||||
|
||||
if (!activeThread) {
|
||||
if (!activeThreadRef.current) {
|
||||
console.error('No active thread')
|
||||
return
|
||||
}
|
||||
@ -165,7 +170,7 @@ export default function useSendChatMessage() {
|
||||
const runtimeParams = toRuntimeParams(activeModelParams)
|
||||
const settingParams = toSettingParams(activeModelParams)
|
||||
|
||||
updateThreadWaiting(activeThread.id, true)
|
||||
updateThreadWaiting(activeThreadRef.current.id, true)
|
||||
const prompt = message.trim()
|
||||
setCurrentPrompt('')
|
||||
setEditPrompt('')
|
||||
@ -187,7 +192,7 @@ export default function useSendChatMessage() {
|
||||
}
|
||||
|
||||
const messages: ChatCompletionMessage[] = [
|
||||
activeThread.assistants[0]?.instructions,
|
||||
activeThreadRef.current.assistants[0]?.instructions,
|
||||
]
|
||||
.filter((e) => e && e.trim() !== '')
|
||||
.map<ChatCompletionMessage>((instructions) => {
|
||||
@ -218,7 +223,7 @@ export default function useSendChatMessage() {
|
||||
? {
|
||||
type: ChatCompletionMessageContentType.Doc,
|
||||
doc_url: {
|
||||
url: `threads/${activeThread.id}/files/${msgId}.pdf`,
|
||||
url: `threads/${activeThreadRef.current.id}/files/${msgId}.pdf`,
|
||||
},
|
||||
}
|
||||
: null,
|
||||
@ -236,13 +241,14 @@ export default function useSendChatMessage() {
|
||||
])
|
||||
)
|
||||
|
||||
let modelRequest = selectedModel ?? activeThread.assistants[0].model
|
||||
let modelRequest =
|
||||
selectedModel ?? activeThreadRef.current.assistants[0].model
|
||||
if (runtimeParams.stream == null) {
|
||||
runtimeParams.stream = true
|
||||
}
|
||||
// Add middleware to the model request with tool retrieval enabled
|
||||
if (
|
||||
activeThread.assistants[0].tools?.some(
|
||||
activeThreadRef.current.assistants[0].tools?.some(
|
||||
(tool: AssistantTool) => tool.type === 'retrieval' && tool.enabled
|
||||
)
|
||||
) {
|
||||
@ -260,14 +266,14 @@ export default function useSendChatMessage() {
|
||||
const messageRequest: MessageRequest = {
|
||||
id: msgId,
|
||||
type: MessageRequestType.Thread,
|
||||
threadId: activeThread.id,
|
||||
threadId: activeThreadRef.current.id,
|
||||
messages,
|
||||
model: {
|
||||
...modelRequest,
|
||||
settings: settingParams,
|
||||
parameters: runtimeParams,
|
||||
},
|
||||
thread: activeThread,
|
||||
thread: activeThreadRef.current,
|
||||
}
|
||||
|
||||
const timestamp = Date.now()
|
||||
@ -307,7 +313,7 @@ export default function useSendChatMessage() {
|
||||
|
||||
const threadMessage: ThreadMessage = {
|
||||
id: msgId,
|
||||
thread_id: activeThread.id,
|
||||
thread_id: activeThreadRef.current.id,
|
||||
role: ChatCompletionRole.User,
|
||||
status: MessageStatus.Ready,
|
||||
created: timestamp,
|
||||
@ -322,10 +328,10 @@ export default function useSendChatMessage() {
|
||||
}
|
||||
|
||||
const updatedThread: Thread = {
|
||||
...activeThread,
|
||||
...activeThreadRef.current,
|
||||
updated: timestamp,
|
||||
metadata: {
|
||||
...(activeThread.metadata ?? {}),
|
||||
...(activeThreadRef.current.metadata ?? {}),
|
||||
lastMessage: prompt,
|
||||
},
|
||||
}
|
||||
@ -337,9 +343,10 @@ export default function useSendChatMessage() {
|
||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||
?.addNewMessage(threadMessage)
|
||||
|
||||
const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id
|
||||
const modelId =
|
||||
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id
|
||||
|
||||
if (activeModel?.id !== modelId) {
|
||||
if (modelRef.current?.id !== modelId) {
|
||||
setQueuedMessage(true)
|
||||
startModel(modelId)
|
||||
await waitForModelStarting(modelId)
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.47.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"csstype": "^3.0.10",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-scroll-to-bottom": "^4.2.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
@ -66,11 +67,11 @@
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ import { setImportModelStageAtom } from '@/hooks/useImportModel'
|
||||
import ExploreModelList from './ExploreModelList'
|
||||
import { HuggingFaceModal } from './HuggingFaceModal'
|
||||
|
||||
import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||
import {
|
||||
configuredModelsAtom,
|
||||
downloadedModelsAtom,
|
||||
@ -37,8 +36,6 @@ const ExploreModelsScreen = () => {
|
||||
const [showHuggingFaceModal, setShowHuggingFaceModal] = useState(false)
|
||||
const setImportModelStage = useSetAtom(setImportModelStageAtom)
|
||||
|
||||
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||
|
||||
const filteredModels = configuredModels.filter((x) => {
|
||||
if (sortSelected === 'Downloaded') {
|
||||
return (
|
||||
@ -59,10 +56,6 @@ const ExploreModelsScreen = () => {
|
||||
setImportModelStage('SELECTING_MODEL')
|
||||
}, [setImportModelStage])
|
||||
|
||||
const onHuggingFaceConverterClick = () => {
|
||||
setShowHuggingFaceModal(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex h-full w-full overflow-y-auto bg-background"
|
||||
@ -103,7 +96,7 @@ const ExploreModelsScreen = () => {
|
||||
Import Model
|
||||
</Button>
|
||||
</div>
|
||||
{experimentalFeature && (
|
||||
{/* {experimentalFeature && (
|
||||
<div className="text-center">
|
||||
<p
|
||||
onClick={onHuggingFaceConverterClick}
|
||||
@ -112,7 +105,7 @@ const ExploreModelsScreen = () => {
|
||||
Convert from Hugging Face
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto w-4/5 py-6">
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import { ImportingModel } from '@janhq/core/.'
|
||||
import { ImportingModel } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { AlertCircle } from 'lucide-react'
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
height: 24px;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user