@ -16,7 +16,6 @@ on:
|
||||
jobs:
|
||||
set-public-provider:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
outputs:
|
||||
public_provider: ${{ steps.set-public-provider.outputs.public_provider }}
|
||||
ref: ${{ steps.set-public-provider.outputs.ref }}
|
||||
@ -69,9 +68,9 @@ jobs:
|
||||
if: github.event_name == 'schedule'
|
||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||
with:
|
||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||
ref: refs/heads/dev
|
||||
build_reason: Nightly
|
||||
push_to_branch: main
|
||||
push_to_branch: dev
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
noti-discord-manual-and-update-url-readme:
|
||||
@ -80,7 +79,7 @@ jobs:
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'cloudflare-r2'
|
||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||
with:
|
||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||
ref: refs/heads/dev
|
||||
build_reason: Manual
|
||||
push_to_branch: main
|
||||
push_to_branch: dev
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||
with:
|
||||
ref: refs/heads/main
|
||||
ref: refs/heads/dev
|
||||
build_reason: Nightly
|
||||
push_to_branch: main
|
||||
push_to_branch: dev
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
mv /tmp/package.json electron/package.json
|
||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
jq '.build.publish = [{"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
|
||||
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
|
||||
cat electron/package.json
|
||||
|
||||
|
||||
4
.github/workflows/template-build-macos.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: build-linux-x64
|
||||
name: build-macos
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@ -70,7 +70,7 @@ jobs:
|
||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
|
||||
jq '.build.publish = [{"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
|
||||
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
|
||||
cat electron/package.json
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ jobs:
|
||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
|
||||
jq '.build.publish = [{"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
|
||||
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 '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
|
||||
|
||||
4
.gitignore
vendored
@ -23,4 +23,6 @@ extensions/inference-nitro-extension/bin/*/*.metal
|
||||
extensions/inference-nitro-extension/bin/*/*.exe
|
||||
extensions/inference-nitro-extension/bin/*/*.dll
|
||||
extensions/inference-nitro-extension/bin/*/*.exp
|
||||
extensions/inference-nitro-extension/bin/*/*.lib
|
||||
extensions/inference-nitro-extension/bin/*/*.lib
|
||||
extensions/inference-nitro-extension/bin/saved-*
|
||||
extensions/inference-nitro-extension/bin/*.tar.gz
|
||||
|
||||
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
1
Makefile
@ -39,6 +39,7 @@ lint: check-file-counts
|
||||
# Testing
|
||||
test: lint
|
||||
yarn build:test
|
||||
yarn test:unit
|
||||
yarn test
|
||||
|
||||
# Builds and publishes the app
|
||||
|
||||
10
README.md
@ -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/0.4.4-159/jan-win-x64-0.4.4-159.exe'>
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.4-170.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/0.4.4-159/jan-mac-x64-0.4.4-159.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.4-170.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/0.4.4-159/jan-mac-arm64-0.4.4-159.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.4-170.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/0.4.4-159/jan-linux-amd64-0.4.4-159.deb'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.4-170.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/0.4.4-159/jan-linux-x86_64-0.4.4-159.AppImage'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.4-170.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
|
||||
3
core/.gitignore
vendored
@ -6,7 +6,4 @@ coverage
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
compiled
|
||||
.awcache
|
||||
.rpt2_cache
|
||||
docs
|
||||
|
||||
7
core/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent",
|
||||
"trailingComma": "es5",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
395
core/README.md
@ -1,348 +1,69 @@
|
||||
## @janhq/core
|
||||
|
||||
> The module includes functions for communicating with core APIs, registering plugin extensions, and exporting type definitions.
|
||||
> This module includes functions for communicating with core APIs, registering app extensions, and exporting type definitions.
|
||||
|
||||
## Usage
|
||||
|
||||
### Import the package
|
||||
|
||||
```js
|
||||
// javascript
|
||||
const core = require("@janhq/core");
|
||||
|
||||
// typescript
|
||||
// Web / extension runtime
|
||||
import * as core from "@janhq/core";
|
||||
|
||||
// Node runtime
|
||||
import * as node from "@janhq/core/node";
|
||||
```
|
||||
|
||||
### Register Plugin Extensions
|
||||
|
||||
Every plugin must define an `init` function in its main entry file to initialize the plugin and register its extensions with the Jan platform.
|
||||
|
||||
You can `register` any function as a plugin extension using `CoreServiceAPI` below. For example, the `DataService.GetConversations` entry name can be used to register a function that retrieves conversations.
|
||||
|
||||
Once the extension is registered, it can be used by other plugins or components in the Jan platform. For example, a UI component might use the DataService.GetConversations extension to retrieve a list of conversations to display to the user.
|
||||
|
||||
```js
|
||||
import { RegisterExtensionPoint, DataService } from "@janhq/core";
|
||||
|
||||
function getConversations() {
|
||||
// Your logic here
|
||||
}
|
||||
|
||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
||||
register(DataService.GetConversations, getConversations.name, getConversations);
|
||||
}
|
||||
```
|
||||
|
||||
### Interact with Local Data Storage
|
||||
|
||||
The Core API allows you to interact with local data storage. Here are a couple of examples of how you can use it:
|
||||
|
||||
#### Insert Data
|
||||
|
||||
You can use the store.insertOne function to insert data into a specific collection in the local data store.
|
||||
|
||||
```js
|
||||
import { store } from "@janhq/core";
|
||||
|
||||
function insertData() {
|
||||
store.insertOne("conversations", { name: "meow" });
|
||||
// Insert a new document with { name: "meow" } into the "conversations" collection.
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Data
|
||||
|
||||
To retrieve data from a collection in the local data store, you can use the `store.findOne` or `store.findMany` function. It allows you to filter and retrieve documents based on specific criteria.
|
||||
|
||||
store.getOne(collectionName, key) retrieves a single document that matches the provided key in the specified collection.
|
||||
store.getMany(collectionName, selector, sort) retrieves multiple documents that match the provided selector in the specified collection.
|
||||
|
||||
```js
|
||||
import { store } from "@janhq/core";
|
||||
|
||||
function getData() {
|
||||
const selector = { name: "meow" };
|
||||
const data = store.findMany("conversations", selector);
|
||||
// Retrieve documents from the "conversations" collection that match the filter.
|
||||
}
|
||||
```
|
||||
|
||||
#### Update Data
|
||||
|
||||
You can update data in the local store using these functions:
|
||||
|
||||
store.updateOne(collectionName, key, update) updates a single document that matches the provided key in the specified collection.
|
||||
store.updateMany(collectionName, selector, update) updates multiple documents that match the provided selector in the specified collection.
|
||||
|
||||
```js
|
||||
function updateData() {
|
||||
const selector = { name: "meow" };
|
||||
const update = { name: "newName" };
|
||||
store.updateOne("conversations", selector, update);
|
||||
// Update a document in the "conversations" collection.
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete Data
|
||||
|
||||
You can delete data from the local data store using these functions:
|
||||
|
||||
store.deleteOne(collectionName, key) deletes a single document that matches the provided key in the specified collection.
|
||||
store.deleteMany(collectionName, selector) deletes multiple documents that match the provided selector in the specified collection.
|
||||
|
||||
```js
|
||||
function deleteData() {
|
||||
const selector = { name: "meow" };
|
||||
store.deleteOne("conversations", selector);
|
||||
// Delete a document from the "conversations" collection.
|
||||
}
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
You can subscribe to NewMessageRequest events by defining a function to handle the event and registering it with the events object:
|
||||
|
||||
```js
|
||||
import { events } from "@janhq/core";
|
||||
|
||||
function handleMessageRequest(message: NewMessageRequest) {
|
||||
// Your logic here. For example:
|
||||
// const response = openai.createChatCompletion({...})
|
||||
}
|
||||
function registerListener() {
|
||||
events.on(EventName.OnNewMessageRequest, handleMessageRequest);
|
||||
}
|
||||
// Register the listener function with the relevant extension points.
|
||||
export function init({ register }) {
|
||||
registerListener();
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we're defining a function called handleMessageRequest that takes a NewMessageRequest object as its argument. We're also defining a function called registerListener that registers the handleMessageRequest function as a listener for NewMessageRequest events using the on method of the events object.
|
||||
|
||||
```js
|
||||
import { events } from "@janhq/core";
|
||||
|
||||
function handleMessageRequest(data: NewMessageRequest) {
|
||||
// Your logic here. For example:
|
||||
const response = openai.createChatCompletion({...})
|
||||
const message: NewMessageResponse = {
|
||||
...data,
|
||||
message: response.data.choices[0].message.content
|
||||
}
|
||||
// Now emit event so the app can display in the conversation
|
||||
events.emit(EventName.OnNewMessageResponse, message)
|
||||
}
|
||||
```
|
||||
|
||||
### Preferences
|
||||
|
||||
To register plugin preferences, you can use the preferences object from the @janhq/core package. Here's an example of how to register and retrieve plugin preferences:
|
||||
|
||||
```js
|
||||
import { PluginService, preferences } from "@janhq/core";
|
||||
|
||||
const pluginName = "your-first-plugin";
|
||||
const preferenceKey = "";
|
||||
const preferenceName = "Your First Preference";
|
||||
const preferenceDescription = "This is for example only";
|
||||
const defaultValue = "";
|
||||
|
||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
||||
// Register preference update handlers. E.g. update plugin instance with new configuration
|
||||
register(PluginService.OnPreferencesUpdate, pluginName, onPreferencesUpdate);
|
||||
|
||||
// Register plugin preferences. E.g. Plugin need apiKey to connect to your service
|
||||
preferences.registerPreferences <
|
||||
string >
|
||||
(register, pluginName, preferenceKey, preferenceName, preferenceDescription, defaultValue);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we're registering preference update handlers and plugin preferences using the preferences object. We're also defining a PluginName constant to use as the name of the plugin.
|
||||
|
||||
To retrieve the values of the registered preferences, we're using the get method of the preferences object and passing in the name of the plugin and the name of the preference.
|
||||
|
||||
```js
|
||||
import { preferences } from "@janhq/core";
|
||||
|
||||
const pluginName = "your-first-plugin";
|
||||
const preferenceKey = "apiKey";
|
||||
|
||||
const setup = async () => {
|
||||
// Retrieve apiKey
|
||||
const apiKey: string = (await preferences.get(pluginName, preferenceKey)) ?? "";
|
||||
};
|
||||
```
|
||||
|
||||
### Access Core API
|
||||
|
||||
To access the Core API in your plugin, you can follow the code examples and explanations provided below.
|
||||
|
||||
##### Import Core API and Store Module
|
||||
|
||||
In your main entry code (e.g., `index.ts`), start by importing the necessary modules and functions from the `@janhq/core` library.
|
||||
|
||||
```js
|
||||
// index.ts
|
||||
import * as core from "@janhq/core";
|
||||
```
|
||||
|
||||
#### Perform File Operations
|
||||
|
||||
The Core API also provides functions to perform file operations. Here are a couple of examples:
|
||||
|
||||
#### Download a File
|
||||
|
||||
You can download a file from a specified URL and save it with a given file name using the core.downloadFile function.
|
||||
|
||||
```js
|
||||
function downloadModel(url: string, fileName: string) {
|
||||
core.downloadFile(url, fileName);
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete a File
|
||||
|
||||
To delete a file, you can use the core.deleteFile function, providing the path to the file you want to delete.
|
||||
|
||||
```js
|
||||
function deleteModel(filePath: string) {
|
||||
core.deleteFile(path);
|
||||
}
|
||||
```
|
||||
|
||||
#### Execute plugin module in main process
|
||||
|
||||
To execute a plugin module in the main process of your application, you can follow the steps outlined below.
|
||||
|
||||
##### Import the `core` Object
|
||||
|
||||
In your main process code (e.g., `index.ts`), start by importing the `core` object from the `@janhq/core` library.
|
||||
|
||||
```js
|
||||
// index.ts
|
||||
import * as core from "@janhq/core";
|
||||
```
|
||||
|
||||
##### Define the Module Path
|
||||
|
||||
Specify the path to the plugin module you want to execute. This path should lead to the module file (e.g., module.js) that contains the functions you wish to call.
|
||||
|
||||
```js
|
||||
// index.ts
|
||||
const MODULE_PATH = "data-plugin/dist/module.js";
|
||||
```
|
||||
|
||||
##### Define the Function to Execute
|
||||
|
||||
Create a function that will execute a function defined in your plugin module. In the example provided, the function `getConversationMessages` is created to invoke the `getConvMessages` function from the plugin module.
|
||||
|
||||
```js
|
||||
// index.ts
|
||||
function getConversationMessages(id: number) {
|
||||
return core.invokePluginFunc(MODULE_PATH, "getConvMessages", id);
|
||||
}
|
||||
|
||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
||||
register(DataService.GetConversationMessages, getConversationMessages.name, getConversationMessages);
|
||||
}
|
||||
```
|
||||
|
||||
##### Define Your Plugin Module
|
||||
|
||||
In your plugin module (e.g., module.ts), define the logic for the function you wish to execute. In the example, the function getConvMessages is defined with a placeholder comment indicating where your logic should be implemented.
|
||||
|
||||
```js
|
||||
// module.ts
|
||||
function getConvMessages(id: number) {
|
||||
// Your logic here
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getConvMessages,
|
||||
};
|
||||
```
|
||||
|
||||
## CoreService API
|
||||
|
||||
The `CoreService` type is an exported union type that includes:
|
||||
|
||||
- `StoreService`
|
||||
- `DataService`
|
||||
- `InferenceService`
|
||||
- `ModelManagementService`
|
||||
- `SystemMonitoringService`
|
||||
- `PreferenceService`
|
||||
|
||||
## StoreService
|
||||
|
||||
The `StoreService` enum represents available methods for managing the database store. It includes the following methods:
|
||||
|
||||
- `CreateCollection`: Creates a new collection in the data store.
|
||||
- `DeleteCollection`: Deletes an existing collection from the data store.
|
||||
- `InsertOne`: Inserts a new value into an existing collection in the data store.
|
||||
- `UpdateOne`: Updates an existing value in an existing collection in the data store.
|
||||
- `UpdateMany`: Updates multiple records in a collection in the data store.
|
||||
- `DeleteOne`: Deletes an existing value from an existing collection in the data store.
|
||||
- `DeleteMany`: Deletes multiple records in a collection in the data store.
|
||||
- `FindMany`: Retrieves multiple records from a collection in the data store.
|
||||
- `FindOne`: Retrieves a single record from a collection in the data store.
|
||||
|
||||
## DataService
|
||||
|
||||
The `DataService` enum represents methods related to managing conversations and messages. It includes the following methods:
|
||||
|
||||
- `GetConversations`: Gets a list of conversations from the data store.
|
||||
- `CreateConversation`: Creates a new conversation in the data store.
|
||||
- `DeleteConversation`: Deletes an existing conversation from the data store.
|
||||
- `CreateMessage`: Creates a new message in an existing conversation in the data store.
|
||||
- `UpdateMessage`: Updates an existing message in an existing conversation in the data store.
|
||||
- `GetConversationMessages`: Gets a list of messages for an existing conversation from the data store.
|
||||
|
||||
## InferenceService
|
||||
|
||||
The `InferenceService` enum exports:
|
||||
|
||||
- `InitModel`: Initializes a model for inference.
|
||||
- `StopModel`: Stops a running inference model.
|
||||
|
||||
## ModelManagementService
|
||||
|
||||
The `ModelManagementService` enum provides methods for managing models:
|
||||
|
||||
- `GetDownloadedModels`: Gets a list of downloaded models.
|
||||
- `GetAvailableModels`: Gets a list of available models from data store.
|
||||
- `DeleteModel`: Deletes a downloaded model.
|
||||
- `DownloadModel`: Downloads a model from the server.
|
||||
- `SearchModels`: Searches for models on the server.
|
||||
- `GetConfiguredModels`: Gets configured models from the data store.
|
||||
- `StoreModel`: Stores a model in the data store.
|
||||
- `UpdateFinishedDownloadAt`: Updates the finished download time for a model in the data store.
|
||||
- `GetUnfinishedDownloadModels`: Gets a list of unfinished download models from the data store.
|
||||
- `GetFinishedDownloadModels`: Gets a list of finished download models from the data store.
|
||||
- `DeleteDownloadModel`: Deletes a downloaded model from the data store.
|
||||
- `GetModelById`: Gets a model by its ID from the data store.
|
||||
|
||||
## PreferenceService
|
||||
|
||||
The `PreferenceService` enum provides methods for managing plugin preferences:
|
||||
|
||||
- `ExperimentComponent`: Represents the UI experiment component for a testing function.
|
||||
|
||||
## SystemMonitoringService
|
||||
|
||||
The `SystemMonitoringService` enum includes methods for monitoring system resources:
|
||||
|
||||
- `GetResourcesInfo`: Gets information about system resources.
|
||||
- `GetCurrentLoad`: Gets the current system load.
|
||||
|
||||
## PluginService
|
||||
|
||||
The `PluginService` enum includes plugin cycle handlers:
|
||||
|
||||
- `OnStart`: Handler for starting. E.g. Create a collection.
|
||||
- `OnPreferencesUpdate`: Handler for preferences update. E.g. Update instances with new configurations.
|
||||
|
||||
For more detailed information on each of these components, please refer to the source code.
|
||||
## Build an Extension
|
||||
|
||||
1. Download an extension template, for example, [https://github.com/janhq/extension-template](https://github.com/janhq/extension-template).
|
||||
|
||||
2. Update the source code:
|
||||
1. Open `index.ts` in your code editor.
|
||||
2. Rename the extension class from `SampleExtension` to your preferred extension name.
|
||||
3. Import modules from the core package.
|
||||
```ts
|
||||
import * as core from "@janhq/core";
|
||||
```
|
||||
4. In the `onLoad()` method, add your code:
|
||||
```ts
|
||||
// Example of listening to app events and providing customized inference logic:
|
||||
import * as core from "@janhq/core";
|
||||
|
||||
export default class MyExtension extends BaseExtension {
|
||||
// On extension load
|
||||
onLoad() {
|
||||
core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this));
|
||||
}
|
||||
|
||||
// Customized inference logic
|
||||
private static inference(incomingMessage: MessageRequestData) {
|
||||
|
||||
// Prepare customized message content
|
||||
const content: ThreadContent = {
|
||||
type: ContentType.Text,
|
||||
text: {
|
||||
value: "I'm Jan Assistant!",
|
||||
annotations: [],
|
||||
},
|
||||
};
|
||||
|
||||
// Modify message and send out
|
||||
const outGoingMessage: ThreadMessage = {
|
||||
...incomingMessage,
|
||||
content
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Build the extension:
|
||||
1. Navigate to the extension directory.
|
||||
2. Install dependencies.
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
3. Compile the source code. The following command keeps running in the terminal and rebuilds the extension when you modify the source code.
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
4. Select the generated .tgz from Jan > Settings > Extension > Manual Installation.
|
||||
7
core/jest.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleNameMapper: {
|
||||
'@/(.*)': '<rootDir>/src/$1',
|
||||
},
|
||||
}
|
||||
@ -12,16 +12,10 @@
|
||||
"module": "dist/core.es5.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"types"
|
||||
],
|
||||
"author": "Jan <service@jan.ai>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/core.umd.js",
|
||||
"./sdk": "./dist/core.umd.js",
|
||||
@ -45,66 +39,23 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
|
||||
"test": "jest",
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||
"start": "rollup -c rollup.config.ts -w"
|
||||
},
|
||||
"lint-staged": {
|
||||
"{src,test}/**/*.ts": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".(ts|tsx)": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
"/test/"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 90,
|
||||
"functions": 95,
|
||||
"lines": 95,
|
||||
"statements": 95
|
||||
}
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"src/*.{js,ts}"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^25.4.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^12.0.2",
|
||||
"eslint-plugin-jest": "^23.8.2",
|
||||
"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-node": "^7.0.1",
|
||||
"ts-jest": "^26.1.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
|
||||
@ -7,12 +7,16 @@ export enum AppRoute {
|
||||
openExternalUrl = 'openExternalUrl',
|
||||
openAppDirectory = 'openAppDirectory',
|
||||
openFileExplore = 'openFileExplorer',
|
||||
selectDirectory = 'selectDirectory',
|
||||
getAppConfigurations = 'getAppConfigurations',
|
||||
updateAppConfiguration = 'updateAppConfiguration',
|
||||
relaunch = 'relaunch',
|
||||
joinPath = 'joinPath',
|
||||
baseName = 'baseName',
|
||||
startServer = 'startServer',
|
||||
stopServer = 'stopServer',
|
||||
log = 'log'
|
||||
log = 'log',
|
||||
logServer = 'logServer',
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
@ -55,7 +59,7 @@ export enum FileSystemRoute {
|
||||
}
|
||||
export enum FileManagerRoute {
|
||||
syncFile = 'syncFile',
|
||||
getUserSpace = 'getUserSpace',
|
||||
getJanDataFolderPath = 'getJanDataFolderPath',
|
||||
getResourcePath = 'getResourcePath',
|
||||
fileStat = 'fileStat',
|
||||
}
|
||||
|
||||
@ -19,10 +19,12 @@ const executeOnMain: (extension: string, method: string, ...args: any[]) => Prom
|
||||
* Downloads a file from a URL and saves it to the local file system.
|
||||
* @param {string} url - The URL of the file to download.
|
||||
* @param {string} fileName - The name to use for the downloaded file.
|
||||
* @param {object} network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
||||
*/
|
||||
const downloadFile: (url: string, fileName: string) => Promise<any> = (url, fileName) =>
|
||||
global.core?.api?.downloadFile(url, fileName)
|
||||
const downloadFile: (url: string, fileName: string, network?: { proxy?: string, ignoreSSL?: boolean }) => Promise<any> = (url, fileName, network) => {
|
||||
return global.core?.api?.downloadFile(url, fileName, network)
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the download of a specific file.
|
||||
@ -33,10 +35,11 @@ const abortDownload: (fileName: string) => Promise<any> = (fileName) =>
|
||||
global.core.api?.abortDownload(fileName)
|
||||
|
||||
/**
|
||||
* Gets the user space path.
|
||||
* @returns {Promise<any>} A Promise that resolves with the user space path.
|
||||
* Gets Jan's data folder path.
|
||||
*
|
||||
* @returns {Promise<string>} A Promise that resolves with Jan's data folder path.
|
||||
*/
|
||||
const getUserSpace = (): Promise<string> => global.core.api?.getUserSpace()
|
||||
const getJanDataFolderPath = (): Promise<string> => global.core.api?.getJanDataFolderPath()
|
||||
|
||||
/**
|
||||
* Opens the file explorer at a specific path.
|
||||
@ -101,12 +104,12 @@ export {
|
||||
executeOnMain,
|
||||
downloadFile,
|
||||
abortDownload,
|
||||
getUserSpace,
|
||||
getJanDataFolderPath,
|
||||
openFileExplorer,
|
||||
getResourcePath,
|
||||
joinPath,
|
||||
openExternalUrl,
|
||||
baseName,
|
||||
log,
|
||||
FileStat
|
||||
FileStat,
|
||||
}
|
||||
|
||||
@ -1,28 +1,3 @@
|
||||
// TODO: refactor EventName to use the events defined in /types
|
||||
/**
|
||||
* The `EventName` enumeration contains the names of all the available events in the Jan platform.
|
||||
*/
|
||||
export enum EventName {
|
||||
/** The `OnMessageSent` event is emitted when a message is sent. */
|
||||
OnMessageSent = 'OnMessageSent',
|
||||
/** The `OnMessageResponse` event is emitted when a message is received. */
|
||||
OnMessageResponse = 'OnMessageResponse',
|
||||
/** The `OnMessageUpdate` event is emitted when a message is updated. */
|
||||
OnMessageUpdate = 'OnMessageUpdate',
|
||||
/** The `OnModelInit` event is emitted when a model inits. */
|
||||
OnModelInit = 'OnModelInit',
|
||||
/** The `OnModelReady` event is emitted when a model ready. */
|
||||
OnModelReady = 'OnModelReady',
|
||||
/** The `OnModelFail` event is emitted when a model fails loading. */
|
||||
OnModelFail = 'OnModelFail',
|
||||
/** The `OnModelStop` event is emitted when a model start to stop. */
|
||||
OnModelStop = 'OnModelStop',
|
||||
/** The `OnModelStopped` event is emitted when a model stopped ok. */
|
||||
OnModelStopped = 'OnModelStopped',
|
||||
/** The `OnInferenceStopped` event is emitted when a inference is stopped. */
|
||||
OnInferenceStopped = 'OnInferenceStopped',
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an observer for an event.
|
||||
*
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum ExtensionType {
|
||||
export enum ExtensionTypeEnum {
|
||||
Assistant = "assistant",
|
||||
Conversational = "conversational",
|
||||
Inference = "inference",
|
||||
@ -6,17 +6,22 @@ export enum ExtensionType {
|
||||
SystemMonitoring = "systemMonitoring",
|
||||
}
|
||||
|
||||
export interface ExtensionType {
|
||||
type(): ExtensionTypeEnum | undefined;
|
||||
}
|
||||
/**
|
||||
* Represents a base extension.
|
||||
* This class should be extended by any class that represents an extension.
|
||||
*/
|
||||
export abstract class BaseExtension {
|
||||
export abstract class BaseExtension implements ExtensionType {
|
||||
/**
|
||||
* Returns the type of the extension.
|
||||
* @returns {ExtensionType} The type of the extension
|
||||
* Undefined means its not extending any known extension by the application.
|
||||
*/
|
||||
abstract type(): ExtensionType | undefined;
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Called when the extension is loaded.
|
||||
* Any initialization logic for the extension should be put here.
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { Assistant, AssistantInterface } from '../index'
|
||||
import { BaseExtension } from '../extension'
|
||||
import { Assistant, AssistantInterface } from "../index";
|
||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
||||
|
||||
/**
|
||||
* Assistant extension for managing assistants.
|
||||
* @extends BaseExtension
|
||||
*/
|
||||
export abstract class AssistantExtension extends BaseExtension implements AssistantInterface {
|
||||
abstract createAssistant(assistant: Assistant): Promise<void>
|
||||
abstract deleteAssistant(assistant: Assistant): Promise<void>
|
||||
abstract getAssistants(): Promise<Assistant[]>
|
||||
/**
|
||||
* Assistant extension type.
|
||||
*/
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return ExtensionTypeEnum.Assistant;
|
||||
}
|
||||
|
||||
abstract createAssistant(assistant: Assistant): Promise<void>;
|
||||
abstract deleteAssistant(assistant: Assistant): Promise<void>;
|
||||
abstract getAssistants(): Promise<Assistant[]>;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Thread, ThreadInterface, ThreadMessage, MessageInterface } from '../index'
|
||||
import { BaseExtension } from '../extension'
|
||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||
|
||||
/**
|
||||
* Conversational extension. Persists and retrieves conversations.
|
||||
@ -10,6 +10,13 @@ export abstract class ConversationalExtension
|
||||
extends BaseExtension
|
||||
implements ThreadInterface, MessageInterface
|
||||
{
|
||||
/**
|
||||
* Conversation extension type.
|
||||
*/
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return ExtensionTypeEnum.Conversational;
|
||||
}
|
||||
|
||||
abstract getThreads(): Promise<Thread[]>
|
||||
abstract saveThread(thread: Thread): Promise<void>
|
||||
abstract deleteThread(threadId: string): Promise<void>
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { InferenceInterface, MessageRequest, ThreadMessage } from '../index'
|
||||
import { BaseExtension } from '../extension'
|
||||
import { InferenceInterface, MessageRequest, ThreadMessage } from "../index";
|
||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
||||
|
||||
/**
|
||||
* Inference extension. Start, stop and inference models.
|
||||
*/
|
||||
export abstract class InferenceExtension extends BaseExtension implements InferenceInterface {
|
||||
abstract inference(data: MessageRequest): Promise<ThreadMessage>
|
||||
/**
|
||||
* Inference extension type.
|
||||
*/
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return ExtensionTypeEnum.Inference;
|
||||
}
|
||||
|
||||
abstract inference(data: MessageRequest): Promise<ThreadMessage>;
|
||||
}
|
||||
|
||||
@ -1,14 +1,24 @@
|
||||
import { BaseExtension } from '../extension'
|
||||
import { Model, ModelInterface } from '../index'
|
||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
||||
import { Model, ModelInterface } from "../index";
|
||||
|
||||
/**
|
||||
* Model extension for managing models.
|
||||
*/
|
||||
export abstract class ModelExtension extends BaseExtension implements ModelInterface {
|
||||
abstract downloadModel(model: Model): Promise<void>
|
||||
abstract cancelModelDownload(modelId: string): Promise<void>
|
||||
abstract deleteModel(modelId: string): Promise<void>
|
||||
abstract saveModel(model: Model): Promise<void>
|
||||
abstract getDownloadedModels(): Promise<Model[]>
|
||||
abstract getConfiguredModels(): Promise<Model[]>
|
||||
/**
|
||||
* Model extension type.
|
||||
*/
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return ExtensionTypeEnum.Model;
|
||||
}
|
||||
|
||||
abstract downloadModel(
|
||||
model: Model,
|
||||
network?: { proxy: string; ignoreSSL?: boolean },
|
||||
): Promise<void>;
|
||||
abstract cancelModelDownload(modelId: string): Promise<void>;
|
||||
abstract deleteModel(modelId: string): Promise<void>;
|
||||
abstract saveModel(model: Model): Promise<void>;
|
||||
abstract getDownloadedModels(): Promise<Model[]>;
|
||||
abstract getConfiguredModels(): Promise<Model[]>;
|
||||
}
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
import { BaseExtension } from '../extension'
|
||||
import { MonitoringInterface } from '../index'
|
||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
||||
import { MonitoringInterface } from "../index";
|
||||
|
||||
/**
|
||||
* Monitoring extension for system monitoring.
|
||||
* @extends BaseExtension
|
||||
*/
|
||||
export abstract class MonitoringExtension extends BaseExtension implements MonitoringInterface {
|
||||
abstract getResourcesInfo(): Promise<any>
|
||||
abstract getCurrentLoad(): Promise<any>
|
||||
/**
|
||||
* Monitoring extension type.
|
||||
*/
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return ExtensionTypeEnum.SystemMonitoring;
|
||||
}
|
||||
|
||||
abstract getResourcesInfo(): Promise<any>;
|
||||
abstract getCurrentLoad(): Promise<any>;
|
||||
}
|
||||
|
||||
@ -2,13 +2,10 @@ import fs from 'fs'
|
||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||
import { join } from 'path'
|
||||
import { ContentType, MessageStatus, Model, ThreadMessage } from './../../../index'
|
||||
|
||||
const os = require('os')
|
||||
|
||||
const path = join(os.homedir(), 'jan')
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
|
||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||
const directoryPath = join(path, configuration.dirName)
|
||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||
try {
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
console.debug('model folder not found')
|
||||
@ -72,7 +69,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
||||
}
|
||||
}
|
||||
|
||||
const directoryPath = join(path, configuration.dirName)
|
||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||
try {
|
||||
const data = await retrieveBuilder(configuration, id)
|
||||
if (!data) {
|
||||
@ -94,7 +91,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
||||
}
|
||||
|
||||
export const getMessages = async (threadId: string): Promise<ThreadMessage[]> => {
|
||||
const threadDirPath = join(path, 'threads', threadId)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const messageFile = 'messages.jsonl'
|
||||
try {
|
||||
const files: string[] = fs.readdirSync(threadDirPath)
|
||||
@ -155,7 +152,7 @@ export const createThread = async (thread: any) => {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
}
|
||||
const threadDirPath = join(path, 'threads', updatedThread.id)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
@ -189,7 +186,7 @@ export const updateThread = async (threadId: string, thread: any) => {
|
||||
updated: Date.now(),
|
||||
}
|
||||
try {
|
||||
const threadDirPath = join(path, 'threads', updatedThread.id)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
@ -231,7 +228,7 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
],
|
||||
}
|
||||
|
||||
const threadDirPath = join(path, 'threads', threadId)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const threadMessagePath = join(threadDirPath, threadMessagesFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
@ -246,7 +243,12 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadModel = async (modelId: string) => {
|
||||
export const downloadModel = async (
|
||||
modelId: string,
|
||||
network?: { proxy?: string; ignoreSSL?: boolean }
|
||||
) => {
|
||||
const strictSSL = !network?.ignoreSSL
|
||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
||||
const model = await retrieveBuilder(JanApiRouteConfiguration.models, modelId)
|
||||
if (!model || model.object !== 'model') {
|
||||
return {
|
||||
@ -254,7 +256,7 @@ export const downloadModel = async (modelId: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const directoryPath = join(path, 'models', modelId)
|
||||
const directoryPath = join(getJanDataFolderPath(), 'models', modelId)
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
fs.mkdirSync(directoryPath)
|
||||
}
|
||||
@ -263,7 +265,7 @@ export const downloadModel = async (modelId: string) => {
|
||||
const modelBinaryPath = join(directoryPath, modelId)
|
||||
|
||||
const request = require('request')
|
||||
const rq = request(model.source_url)
|
||||
const rq = request({ url: model.source_url, strictSSL, proxy })
|
||||
const progress = require('request-progress')
|
||||
progress(rq, {})
|
||||
.on('progress', function (state: any) {
|
||||
@ -314,7 +316,8 @@ export const chatCompletions = async (request: any, reply: any) => {
|
||||
reply.raw.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
'Connection': 'keep-alive',
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
})
|
||||
|
||||
const headers: Record<string, any> = {
|
||||
@ -345,7 +348,7 @@ const getEngineConfiguration = async (engineId: string) => {
|
||||
if (engineId !== 'openai') {
|
||||
return undefined
|
||||
}
|
||||
const directoryPath = join(path, 'engines')
|
||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
||||
const filePath = join(directoryPath, `${engineId}.json`)
|
||||
const data = await fs.readFileSync(filePath, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
|
||||
@ -27,7 +27,7 @@ export const commonRouter = async (app: HttpServer) => {
|
||||
|
||||
// Download Model Routes
|
||||
app.get(`/models/download/:modelId`, async (request: any) =>
|
||||
downloadModel(request.params.modelId),
|
||||
downloadModel(request.params.modelId, { ignoreSSL: request.query.ignoreSSL === 'true', proxy: request.query.proxy }),
|
||||
)
|
||||
|
||||
// Chat Completion Routes
|
||||
|
||||
@ -1,56 +1,58 @@
|
||||
import { DownloadRoute } from '../../../api'
|
||||
import { join } from 'path'
|
||||
import { userSpacePath } from '../../extension/manager'
|
||||
import { DownloadManager } from '../../download'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
import { createWriteStream } from 'fs'
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
import { normalizeFilePath } from "../../path";
|
||||
|
||||
export const downloadRouter = async (app: HttpServer) => {
|
||||
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
||||
const body = JSON.parse(req.body as any)
|
||||
const strictSSL = !(req.query.ignoreSSL === "true");
|
||||
const proxy = req.query.proxy?.startsWith("http") ? req.query.proxy : undefined;
|
||||
const body = JSON.parse(req.body as any);
|
||||
const normalizedArgs = body.map((arg: any) => {
|
||||
if (typeof arg === 'string' && arg.includes('file:/')) {
|
||||
return join(userSpacePath, arg.replace('file:/', ''))
|
||||
if (typeof arg === "string") {
|
||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
||||
}
|
||||
return arg
|
||||
})
|
||||
return arg;
|
||||
});
|
||||
|
||||
const localPath = normalizedArgs[1]
|
||||
const fileName = localPath.split('/').pop() ?? ''
|
||||
const localPath = normalizedArgs[1];
|
||||
const fileName = localPath.split("/").pop() ?? "";
|
||||
|
||||
const request = require('request')
|
||||
const progress = require('request-progress')
|
||||
|
||||
const rq = request(normalizedArgs[0])
|
||||
const request = require("request");
|
||||
const progress = require("request-progress");
|
||||
|
||||
const rq = request({ url: normalizedArgs[0], strictSSL, proxy });
|
||||
progress(rq, {})
|
||||
.on('progress', function (state: any) {
|
||||
console.log('download onProgress', state)
|
||||
.on("progress", function (state: any) {
|
||||
console.log("download onProgress", state);
|
||||
})
|
||||
.on('error', function (err: Error) {
|
||||
console.log('download onError', err)
|
||||
.on("error", function (err: Error) {
|
||||
console.log("download onError", err);
|
||||
})
|
||||
.on('end', function () {
|
||||
console.log('download onEnd')
|
||||
.on("end", function () {
|
||||
console.log("download onEnd");
|
||||
})
|
||||
.pipe(createWriteStream(normalizedArgs[1]))
|
||||
.pipe(createWriteStream(normalizedArgs[1]));
|
||||
|
||||
DownloadManager.instance.setRequest(fileName, rq)
|
||||
})
|
||||
DownloadManager.instance.setRequest(fileName, rq);
|
||||
});
|
||||
|
||||
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
||||
const body = JSON.parse(req.body as any)
|
||||
const body = JSON.parse(req.body as any);
|
||||
const normalizedArgs = body.map((arg: any) => {
|
||||
if (typeof arg === 'string' && arg.includes('file:/')) {
|
||||
return join(userSpacePath, arg.replace('file:/', ''))
|
||||
if (typeof arg === "string") {
|
||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
||||
}
|
||||
return arg
|
||||
})
|
||||
return arg;
|
||||
});
|
||||
|
||||
const localPath = normalizedArgs[0]
|
||||
const fileName = localPath.split('/').pop() ?? ''
|
||||
console.debug('fileName', fileName)
|
||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
||||
rq?.abort()
|
||||
})
|
||||
}
|
||||
const localPath = normalizedArgs[0];
|
||||
const fileName = localPath.split("/").pop() ?? "";
|
||||
const rq = DownloadManager.instance.networkRequests[fileName];
|
||||
DownloadManager.instance.networkRequests[fileName] = undefined;
|
||||
rq?.abort();
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { join, extname } from 'path'
|
||||
import { ExtensionRoute } from '../../../api/index'
|
||||
import { userSpacePath } from '../../extension/manager'
|
||||
import { ModuleManager } from '../../module'
|
||||
import { getActiveExtensions, installExtensions } from '../../extension/store'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
|
||||
import { readdirSync } from 'fs'
|
||||
import { getJanExtensionsPath } from '../../utils'
|
||||
|
||||
export const extensionRouter = async (app: HttpServer) => {
|
||||
// TODO: Share code between node projects
|
||||
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (_req, res) => {
|
||||
const activeExtensions = await getActiveExtensions()
|
||||
res.status(200).send(activeExtensions)
|
||||
})
|
||||
|
||||
app.post(`/${ExtensionRoute.baseExtensions}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.baseExtensions}`, async (_req, res) => {
|
||||
const baseExtensionPath = join(__dirname, '..', '..', '..', 'pre-install')
|
||||
const extensions = readdirSync(baseExtensionPath)
|
||||
.filter((file) => extname(file) === '.tgz')
|
||||
@ -23,7 +23,7 @@ export const extensionRouter = async (app: HttpServer) => {
|
||||
res.status(200).send(extensions)
|
||||
})
|
||||
|
||||
app.post(`/${ExtensionRoute.installExtension}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.installExtension}`, async (req) => {
|
||||
const extensions = req.body as any
|
||||
const installed = await installExtensions(JSON.parse(extensions)[0])
|
||||
return JSON.parse(JSON.stringify(installed))
|
||||
@ -32,7 +32,7 @@ export const extensionRouter = async (app: HttpServer) => {
|
||||
app.post(`/${ExtensionRoute.invokeExtensionFunc}`, async (req, res) => {
|
||||
const args = JSON.parse(req.body as any)
|
||||
console.debug(args)
|
||||
const module = await import(join(userSpacePath, 'extensions', args[0]))
|
||||
const module = await import(join(getJanExtensionsPath(), args[0]))
|
||||
|
||||
ModuleManager.instance.setModule(args[0], module)
|
||||
const method = args[1]
|
||||
|
||||
@ -4,7 +4,7 @@ import { HttpServer } from '../../index'
|
||||
export const fsRouter = async (app: HttpServer) => {
|
||||
app.post(`/app/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {})
|
||||
|
||||
app.post(`/app/${FileManagerRoute.getUserSpace}`, async (request: any, reply: any) => {})
|
||||
app.post(`/app/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) => {})
|
||||
|
||||
app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {})
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FileSystemRoute } from '../../../api'
|
||||
import { join } from 'path'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
import { userSpacePath } from '../../extension/manager'
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
|
||||
export const fsRouter = async (app: HttpServer) => {
|
||||
const moduleName = 'fs'
|
||||
@ -14,7 +14,7 @@ export const fsRouter = async (app: HttpServer) => {
|
||||
return mdl[route](
|
||||
...body.map((arg: any) =>
|
||||
typeof arg === 'string' && arg.includes('file:/')
|
||||
? join(userSpacePath, arg.replace('file:/', ''))
|
||||
? join(getJanDataFolderPath(), arg.replace('file:/', ''))
|
||||
: arg,
|
||||
),
|
||||
)
|
||||
|
||||
@ -103,7 +103,7 @@ export default class Extension {
|
||||
const pacote = await import('pacote')
|
||||
await pacote.extract(
|
||||
this.specifier,
|
||||
join(ExtensionManager.instance.extensionsPath ?? '', this.name ?? ''),
|
||||
join(ExtensionManager.instance.getExtensionsPath() ?? '', this.name ?? ''),
|
||||
this.installOptions,
|
||||
)
|
||||
|
||||
@ -166,9 +166,9 @@ export default class Extension {
|
||||
* @returns the latest available version if a new version is available or false if not.
|
||||
*/
|
||||
async isUpdateAvailable() {
|
||||
return import('pacote').then((pacote) => {
|
||||
if (this.origin) {
|
||||
return pacote.manifest(this.origin).then((mnf) => {
|
||||
return import('pacote').then((pacote) => {
|
||||
if (this.origin) {
|
||||
return pacote.manifest(this.origin).then((mnf) => {
|
||||
return mnf.version !== this.version ? mnf.version : false
|
||||
})
|
||||
}
|
||||
@ -179,8 +179,9 @@ export default class Extension {
|
||||
* Remove extension and refresh renderers.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async uninstall() {
|
||||
const extPath = resolve(ExtensionManager.instance.extensionsPath ?? '', this.name ?? '')
|
||||
async uninstall(): Promise<void> {
|
||||
const path = ExtensionManager.instance.getExtensionsPath()
|
||||
const extPath = resolve(path ?? '', this.name ?? '')
|
||||
await rmdirSync(extPath, { recursive: true })
|
||||
|
||||
this.emitUpdate()
|
||||
|
||||
@ -35,17 +35,17 @@ async function registerExtensionProtocol() {
|
||||
let electron: any = undefined
|
||||
|
||||
try {
|
||||
const moduleName = "electron"
|
||||
const moduleName = 'electron'
|
||||
electron = await import(moduleName)
|
||||
} catch (err) {
|
||||
console.error('Electron is not available')
|
||||
}
|
||||
|
||||
const extensionPath = ExtensionManager.instance.getExtensionsPath()
|
||||
if (electron) {
|
||||
return electron.protocol.registerFileProtocol('extension', (request: any, callback: any) => {
|
||||
const entry = request.url.substr('extension://'.length - 1)
|
||||
|
||||
const url = normalize(ExtensionManager.instance.extensionsPath + entry)
|
||||
const url = normalize(extensionPath + entry)
|
||||
callback({ path: url })
|
||||
})
|
||||
}
|
||||
@ -120,7 +120,7 @@ function loadExtension(ext: any) {
|
||||
* @returns {extensionManager} A set of functions used to manage the extension lifecycle.
|
||||
*/
|
||||
export function getStore() {
|
||||
if (!ExtensionManager.instance.extensionsPath) {
|
||||
if (!ExtensionManager.instance.getExtensionsFile()) {
|
||||
throw new Error(
|
||||
'The extension path has not yet been set up. Please run useExtensions before accessing the store',
|
||||
)
|
||||
@ -133,4 +133,4 @@ export function getStore() {
|
||||
getActiveExtensions,
|
||||
removeExtension,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +1,45 @@
|
||||
import { join, resolve } from "path";
|
||||
import { join, resolve } from 'path'
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os"
|
||||
/**
|
||||
* Manages extension installation and migration.
|
||||
*/
|
||||
|
||||
export const userSpacePath = join(homedir(), "jan");
|
||||
|
||||
export class ExtensionManager {
|
||||
public static instance: ExtensionManager = new ExtensionManager();
|
||||
public static instance: ExtensionManager = new ExtensionManager()
|
||||
|
||||
extensionsPath: string | undefined = join(userSpacePath, "extensions");
|
||||
private extensionsPath: string | undefined
|
||||
|
||||
constructor() {
|
||||
if (ExtensionManager.instance) {
|
||||
return ExtensionManager.instance;
|
||||
return ExtensionManager.instance
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionsPath(): string | undefined {
|
||||
return this.extensionsPath
|
||||
}
|
||||
|
||||
setExtensionsPath(extPath: string) {
|
||||
// Create folder if it does not exist
|
||||
let extDir;
|
||||
let extDir
|
||||
try {
|
||||
extDir = resolve(extPath);
|
||||
if (extDir.length < 2) throw new Error();
|
||||
extDir = resolve(extPath)
|
||||
if (extDir.length < 2) throw new Error()
|
||||
|
||||
if (!existsSync(extDir)) mkdirSync(extDir);
|
||||
if (!existsSync(extDir)) mkdirSync(extDir)
|
||||
|
||||
const extensionsJson = join(extDir, "extensions.json");
|
||||
if (!existsSync(extensionsJson))
|
||||
writeFileSync(extensionsJson, "{}");
|
||||
const extensionsJson = join(extDir, 'extensions.json')
|
||||
if (!existsSync(extensionsJson)) writeFileSync(extensionsJson, '{}')
|
||||
|
||||
this.extensionsPath = extDir;
|
||||
this.extensionsPath = extDir
|
||||
} catch (error) {
|
||||
throw new Error("Invalid path provided to the extensions folder");
|
||||
throw new Error('Invalid path provided to the extensions folder')
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionsFile() {
|
||||
return join(this.extensionsPath ?? "", "extensions.json");
|
||||
return join(this.extensionsPath ?? '', 'extensions.json')
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,3 +6,5 @@ export * from './download'
|
||||
export * from './module'
|
||||
export * from './api'
|
||||
export * from './log'
|
||||
export * from './utils'
|
||||
export * from './path'
|
||||
|
||||
@ -1,22 +1,35 @@
|
||||
import fs from 'fs'
|
||||
import util from 'util'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { getAppLogPath, getServerLogPath } from './utils'
|
||||
|
||||
export const logDir = path.join(os.homedir(), 'jan', 'logs')
|
||||
|
||||
export const log = function (message: string, fileName: string = 'app.log') {
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true })
|
||||
}
|
||||
export const log = function (message: string) {
|
||||
const appLogPath = getAppLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[APP]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
if (fs.existsSync(logDir)) {
|
||||
var log_file = fs.createWriteStream(path.join(logDir, fileName), {
|
||||
if (fs.existsSync(appLogPath)) {
|
||||
var log_file = fs.createWriteStream(appLogPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
log_file.write(util.format(message) + '\n')
|
||||
log_file.close()
|
||||
console.debug(message)
|
||||
}
|
||||
}
|
||||
|
||||
export const logServer = function (message: string) {
|
||||
const serverLogPath = getServerLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[SERVER]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
if (fs.existsSync(serverLogPath)) {
|
||||
var log_file = fs.createWriteStream(serverLogPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
log_file.write(util.format(message) + '\n')
|
||||
|
||||
9
core/src/node/path.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Normalize file path
|
||||
* Remove all file protocol prefix
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export function normalizeFilePath(path: string): string {
|
||||
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, "$2");
|
||||
}
|
||||
103
core/src/node/utils/index.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { AppConfiguration } from "../../types";
|
||||
import { join } from "path";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
|
||||
// TODO: move this to core
|
||||
const configurationFileName = "settings.json";
|
||||
|
||||
// TODO: do no specify app name in framework module
|
||||
const defaultJanDataFolder = join(os.homedir(), "jan");
|
||||
const defaultAppConfig: AppConfiguration = {
|
||||
data_folder: defaultJanDataFolder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Getting App Configurations.
|
||||
*
|
||||
* @returns {AppConfiguration} The app configurations.
|
||||
*/
|
||||
export const getAppConfigurations = (): AppConfiguration => {
|
||||
// Retrieve Application Support folder path
|
||||
// Fallback to user home directory if not found
|
||||
const configurationFile = getConfigurationFilePath();
|
||||
|
||||
if (!fs.existsSync(configurationFile)) {
|
||||
// create default app config if we don't have one
|
||||
console.debug(`App config not found, creating default config at ${configurationFile}`);
|
||||
fs.writeFileSync(configurationFile, JSON.stringify(defaultAppConfig));
|
||||
return defaultAppConfig;
|
||||
}
|
||||
|
||||
try {
|
||||
const appConfigurations: AppConfiguration = JSON.parse(
|
||||
fs.readFileSync(configurationFile, "utf-8"),
|
||||
);
|
||||
return appConfigurations;
|
||||
} catch (err) {
|
||||
console.error(`Failed to read app config, return default config instead! Err: ${err}`);
|
||||
return defaultAppConfig;
|
||||
}
|
||||
};
|
||||
|
||||
const getConfigurationFilePath = () =>
|
||||
join(
|
||||
global.core?.appPath() || process.env[process.platform == "win32" ? "USERPROFILE" : "HOME"],
|
||||
configurationFileName,
|
||||
);
|
||||
|
||||
export const updateAppConfiguration = (configuration: AppConfiguration): Promise<void> => {
|
||||
const configurationFile = getConfigurationFilePath();
|
||||
console.debug("updateAppConfiguration, configurationFile: ", configurationFile);
|
||||
|
||||
fs.writeFileSync(configurationFile, JSON.stringify(configuration));
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get server log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getServerLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
const logFolderPath = join(appConfigurations.data_folder, "logs");
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true });
|
||||
}
|
||||
return join(logFolderPath, "server.log");
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get app log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getAppLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
const logFolderPath = join(appConfigurations.data_folder, "logs");
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true });
|
||||
}
|
||||
return join(logFolderPath, "app.log");
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get data folder path
|
||||
*
|
||||
* @returns {string} The data folder path.
|
||||
*/
|
||||
export const getJanDataFolderPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
return appConfigurations.data_folder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get extension path
|
||||
*
|
||||
* @returns {string} The extensions path.
|
||||
*/
|
||||
export const getJanExtensionsPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
return join(appConfigurations.data_folder, "extensions");
|
||||
};
|
||||
3
core/src/types/config/appConfigEntity.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type AppConfiguration = {
|
||||
data_folder: string
|
||||
}
|
||||
1
core/src/types/config/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './appConfigEntity'
|
||||
@ -5,3 +5,4 @@ export * from './message'
|
||||
export * from './inference'
|
||||
export * from './monitoring'
|
||||
export * from './file'
|
||||
export * from './config'
|
||||
|
||||
@ -104,6 +104,9 @@ export type ModelSettingParams = {
|
||||
n_parallel?: number
|
||||
cpu_threads?: number
|
||||
prompt_template?: string
|
||||
system_prompt?: string
|
||||
ai_prompt?: string
|
||||
user_prompt?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,9 +7,10 @@ export interface ModelInterface {
|
||||
/**
|
||||
* Downloads a model.
|
||||
* @param model - The model to download.
|
||||
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||
* @returns A Promise that resolves when the model has been downloaded.
|
||||
*/
|
||||
downloadModel(model: Model): Promise<void>
|
||||
downloadModel(model: Model, network?: { ignoreSSL?: boolean, proxy?: string }): Promise<void>
|
||||
|
||||
/**
|
||||
* Cancels the download of a specific model.
|
||||
|
||||
12
core/tests/node/path.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { normalizeFilePath } from "../../src/node/path";
|
||||
|
||||
describe("Test file normalize", () => {
|
||||
test("returns no file protocol prefix on Unix", async () => {
|
||||
expect(normalizeFilePath("file://test.txt")).toBe("test.txt");
|
||||
expect(normalizeFilePath("file:/test.txt")).toBe("test.txt");
|
||||
});
|
||||
test("returns no file protocol prefix on Windows", async () => {
|
||||
expect(normalizeFilePath("file:\\\\test.txt")).toBe("test.txt");
|
||||
expect(normalizeFilePath("file:\\test.txt")).toBe("test.txt");
|
||||
});
|
||||
});
|
||||
@ -13,7 +13,7 @@
|
||||
"declarationDir": "dist/types",
|
||||
"outDir": "dist/lib",
|
||||
"importHelpers": true,
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
"types": ["@types/jest"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
---
|
||||
title: Cultivating Culture in Open Source Contributions Beyond Code
|
||||
description: Discover how to contribute to open-source projects without coding skills. This Jan post guides you through the essentials of non-code contributions, from documentation to community engagement. Learn practical ways to impact open-source initiatives, even if you're new to the field.
|
||||
slug: contributions
|
||||
keywords:
|
||||
[
|
||||
Jan framework,
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
how to contribute to opensource,
|
||||
open source,
|
||||
contributing to open source,
|
||||
]
|
||||
authors:
|
||||
- name: Jan Team
|
||||
tags: [contribution, jan]
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
Ever wondered what ‘contribution’ really means in open-source communities? Calling back to John Picozzi’s presentation at DrupalCon 2022 and his article on [Opensource.com,](https://opensource.com/article/22/8/non-code-contribution-powers-open-source) we look at how his views on contributions align with ours at [Jan](https://jan.ai)
|
||||
|
||||

|
||||
|
||||
[https://jan.ai/about/#open-source](https://jan.ai/about/#open-source)
|
||||
|
||||
#### Broadening the Definition of Contribution - how it benefits everyone
|
||||
|
||||
Contribution in open source isn’t just about coding, and Picozzi nails it with this point.
|
||||
|
||||
At Jan, we’re all about embracing different skills — because that’s what fuels innovation and growth.
|
||||
|
||||
> Contribution? It’s when you take time to push forward ideas, vision, and awareness. Here are some solid ideas and how they’ve been executed before.
|
||||
|
||||
#### Documentation - One Pillar of Contribution
|
||||
|
||||
Remember those ancient storytellers and their crucial role in history? That’s what modern documentation does in tech. It’s the backbone of knowledge transfer. Without it, we’d be lost in translating what our products are all about.
|
||||
|
||||
credit- Janhq/jan repo
|
||||
|
||||
#### Writing and Editing
|
||||
|
||||
Got a knack for words? Awesome! Making complex stuff easy and friendly is a real game-changer. Search around and see if your favourite open-source project has a tone of voice guide or style guide. (We’re currently making one!)
|
||||
|
||||
#### Translating
|
||||
|
||||
Speak another language? You could jump in and help translate documents. You’ll be flinging open doors for heaps of non-native language speakers and will scale awareness and adoption in a pretty big way.
|
||||
|
||||
#### Creating Tutorials and Guides
|
||||
|
||||
Projects thrive on collective knowledge being shared. Maybe your way of contributing is helping newbies find their feet by showcasing how you use the product?
|
||||
|
||||
### Spreading the Word- Storytelling and ‘Marketing
|
||||
|
||||
In the OS world, marketing is more about storytelling and community building than selling.
|
||||
|
||||
Here’s what can make an impact that isn’t code-
|
||||
|
||||
- Social media, blogs, forums- When the folks running open source projects are knee-deep in code, spreading the word might slip their minds. Your tweets, blogs, and comments? They’re more impactful than you think.
|
||||
|
||||
* Content creation -If you’re into making videos or infographics, your skills can tell the project’s story in ways that really stick.
|
||||
|
||||
#### Event Planning and Meetups
|
||||
|
||||
Hosting online meetups or webinars? It’s a great way to share knowledge, connect people, and gather feedback as to how people are connecting with the project.
|
||||
|
||||
### Contributing via ‘Dot Connecting’
|
||||
|
||||
Know people who might be interested or can chip in? Bringing in new energy and ideas is what keeps open source buzzing. Spot an opportunity for a partnership? Go for it!
|
||||
|
||||
Linking up with educational bodies, non-profits, or businesses can work wonders depending on the projects end goals.
|
||||
|
||||
### Why People Contribute to Open Source
|
||||
|
||||
As Picozzi points out, it’s all about embracing every kind of skill. At Jan, we celebrate each team member’s unique contributions.
|
||||
|
||||
It’s about being part of something bigger, honing new skills, and feeling that sense of belonging.
|
||||
|
||||
#### What the Company/Project Gets Out of It
|
||||
|
||||
By valuing non-code contributions, open-source projects, and companies tap into a broader skill set, leading to more well-rounded development and problem-solving. A diverse community is key to Jan’s success, just like Picozzi emphasizes.
|
||||
|
||||
### Challenges in Non-Code Contributing
|
||||
|
||||
Imposter syndrome? We get it, and we’re tackling it by creating a welcoming space for all contributions. Jan’s big on balancing work, life, and contribution — integrating it into our culture without overwhelming the team.
|
||||
|
||||
#### How Do You Start Contributing to OpenSource?
|
||||
|
||||
Just like Nike’s “just do it”, we’re all about making it easy for our team to dive into areas they’re passionate about. Here’s our [labelled issues for contributors](https://github.com/janhq/jan/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
|
||||
#### Here’s the [Jan readme](https://github.com/janhq/jan?tab=readme-ov-file#readme)
|
||||
|
||||
#### [Join us on Discord](https://discord.gg/JPcNRaJyzJ)
|
||||
1
docs/blog/README.md
Normal file
@ -0,0 +1 @@
|
||||
# TODO
|
||||
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 253 KiB |
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: About Jan
|
||||
slug: /about
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Onboarding
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /onboarding
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -14,6 +15,8 @@ keywords:
|
||||
]
|
||||
---
|
||||
|
||||
# Onboarding
|
||||
|
||||
Welcome to Jan! We’re really excited to bring you onboard.
|
||||
|
||||
## Expectations
|
||||
10
docs/docs/about/03-engineering/01-ci-cd.md
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: CI & CD
|
||||
slug: /engineering/ci-cd
|
||||
---
|
||||
|
||||
## Gitflow
|
||||
|
||||
Previously we were trunk based. Now we use the following Gitflow:
|
||||
|
||||
TODO: @van to include her Mermaid diagram
|
||||
80
docs/docs/about/03-engineering/02-qa.mdx
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: QA
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /engineering/qa
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
]
|
||||
---
|
||||
|
||||
### Phase 1: Planning
|
||||
|
||||
#### Definition of Ready (DoR):
|
||||
|
||||
- [ ] **Scope Defined:** The features to be implemented are clearly defined and scoped out.
|
||||
- [ ] **Requirements Gathered:** Gather and document all the necessary requirements for the feature.
|
||||
- [ ] **Stakeholder Input:** Ensure relevant stakeholders have provided input on the document scope and content.
|
||||
|
||||
#### Definition of Done (DoD):
|
||||
|
||||
- [ ] **Document Complete:** All sections of the document are filled out with relevant information.
|
||||
- [ ] **Reviewed by Stakeholders:** The document has been reviewed and approved by stakeholders.
|
||||
- [ ] **Ready for Development:** The document is in a state where developers can use it to begin implementation.
|
||||
|
||||
### Phase 2: Development
|
||||
|
||||
#### Definition of Ready (DoR):
|
||||
|
||||
- [ ] **Task Breakdown:** The development team has broken down tasks based on the document.
|
||||
- [ ] **Communication Plan:** A plan is in place for communication between developers and writers if clarification is needed during implementation.
|
||||
- [ ] **Developer Understanding:** Developers have a clear understanding of the document content.
|
||||
|
||||
#### Definition of Done (DoD):
|
||||
|
||||
- [ ] **Code Implementation:** The feature is implemented according to the document specifications.
|
||||
- [ ] **Developer Testing:**
|
||||
- Unit tests and basic integration tests are completed
|
||||
- Developer also completed self-testing for the feature (please add this as a comment in the ticket, with the tested OS and as much info as possible to reduce overlaping effort).
|
||||
- (AC -> Code Changes -> Impacted scenarios)
|
||||
- [ ] **Communication with Writers:** Developers have communicated any changes or challenges to the writers, and necessary adjustments are made in the document. (Can be through a note in the PR of the feature for writers to take care, or create a separate PR with the change you made for the docs, for writers to review)
|
||||
|
||||
### Phase 3: QA for feature
|
||||
|
||||
#### Definition of Ready (DoR):
|
||||
|
||||
- [ ] **Test Note Defined:** The test note is prepared outlining the testing items.
|
||||
- [ ] **Environment Ready:** PR merged to nightly build, Nightly build notes updated (automatically from pipeline after merged).
|
||||
- [ ] **Status:** Ticket moved to the column Testing and assigning to QA/writers to review.
|
||||
- [ ] **Test Data Prepared:** Relevant test data is prepared for testing the scenarios.
|
||||
|
||||
#### Definition of Done (DoD):
|
||||
|
||||
- [ ] **Test Executed:** All identified test items are executed on different OS, along with exploratory testing.
|
||||
- [ ] **Defects Logged:** Any defects found during testing are resolved / appropriately logged (and approved for future fix).
|
||||
- [ ] **Test Sign-Off:** QA team provides sign-off indicating the completion of testing.
|
||||
|
||||
### Phase 4: Release (DoR)
|
||||
|
||||
- [ ] **Pre-release wait time:** Code change to pre-release version should be frozen for at least X (hrs/days) for Regression testing purpose.
|
||||
- Pre-release cut off on Thu morning for the team to regression test.
|
||||
- Release to production (Stable) during working hour on Mon morning (if no blocker) or Tue morning.
|
||||
- During the release cut off, the nightly build will be paused, to leave room for pre-release build. The build version used for regression test will be notified.
|
||||
- [ ] **Pre-release testing:** A review of the implemented feature has been conducted, a long with regression test (check-list) by the team.
|
||||
- Release checklist cloned from the templat for different OS (with hackMD link)
|
||||
- New key test items from new feature added to the checklist.
|
||||
- Split 3 OS to different team members for testing.
|
||||
- [ ] **Document Updated:** The document is updated based on the review and feedback on any discrepancies or modification needed for this release.
|
||||
- [ ] **Reviewed by Stakeholders:** New feature and the updated document is reviewed and approved by stakeholders. The document is in its final version, reflecting the implemented feature accurately.
|
||||
|
||||
### Notes (WIP)
|
||||
|
||||
- [ ] **API collection run:** to run along with nightly build daily, for critical API validation
|
||||
- [ ] **Automation run:** for regression testing purpose, to reduce manual testing effort for the same items each release on multiple OS.
|
||||
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: Engineering
|
||||
title: MLOps
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /engineering/mlops
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -16,6 +17,8 @@ keywords:
|
||||
|
||||
## Connecting to Rigs
|
||||
|
||||
We have a small data rig you can remote into for R&D and CI.
|
||||
|
||||
### Pritunl Setup
|
||||
|
||||
1. **Install Pritunl**: [Download here](https://client.pritunl.com/#install)
|
||||
@ -137,7 +140,7 @@ cd examples/llama && rm -rf ./llama/7B && mkdir -p ./llama/7B && git clone https
|
||||
python build.py --model_dir ./llama/7B/ --dtype float16 --remove_input_padding --use_gpt_attention_plugin float16 --enable_context_fmha --use_gemm_plugin float16 --use_weight_only --output_dir ./llama/7B/trt_engines/weight_only/1-gpu/
|
||||
```
|
||||
|
||||
4. Run Inference:
|
||||
4. **Run Inference:**
|
||||
|
||||
```bash
|
||||
python3 run.py --max_output_len=2048 --tokenizer_dir ./llama/7B/ --engine_dir=./llama/7B/trt_engines/weight_only/1-gpu/ --input_text "Writing a thesis proposal can be done in 10 simple steps:\nStep 1:"
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Overview
|
||||
slug: /handbook
|
||||
title: R&D
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /engineering/research
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -15,4 +15,6 @@ keywords:
|
||||
]
|
||||
---
|
||||
|
||||
Welcome to Jan Handbook! We’re really excited to bring you onboard.
|
||||
## Foundry Best Practices
|
||||
|
||||
@alan/rex TODO
|
||||
@ -0,0 +1,136 @@
|
||||
---
|
||||
title: |
|
||||
10/1/24: Bitdefender False Positive Flag (Resolved)
|
||||
slug: /postmortems/january-10-2024-bitdefender-false-positive-flag
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
postmortem,
|
||||
incident,
|
||||
flagging issue,
|
||||
]
|
||||
---
|
||||
|
||||
Following the recent incident related to Jan version 0.4.4 triggering Bitdefender on Windows with Gen:Variant.Tedy.258323 on January 10, 2024, we wanted to provide a comprehensive postmortem and outline the necessary follow-up actions.
|
||||
|
||||
## Incident Overview
|
||||
|
||||
### Bug Description
|
||||
|
||||
Jan 0.4.4 installation on Windows triggered Bitdefender to flag it as infected with Gen:Variant.Tedy.258323, leading to automatic quarantine.
|
||||
|
||||
### Affected Antivirus
|
||||
|
||||
- McAfee / Microsoft Defender was unaffected
|
||||
- Bitdefender consistently flagged the issue.
|
||||
|
||||
### Incident Timeline
|
||||
|
||||
- _10 Jan, 2:18 am SGT:_ Hawke flags up Malware antivirus errors for 0.4.4 installation on Windows computers.
|
||||
- _10 Jan, 2:21 am SGT:_ @0xSage responds in Discord.
|
||||
- _10 Jan, 2:35 am SGT:_ Hawke confirms multiple people have experienced this error on fresh installs.
|
||||
- _10 Jan, 2:41 am SGT:_ @louis-jan and @dan-jan revert 0.4.4 out of an abundance of caution.
|
||||
- _Incident ongoing:_ To triage and investigate the next day.
|
||||
- _10 Jan, 11:36 am SGT:_ @Hien has investigated all versions of Nitro and conducted scans using Bitdefender. Only the 2 latest versions raised warnings (0.2.7, 0.2.8).
|
||||
- _10 Jan, 12:44 pm SGT:_ @Hien tested again for the 0.2.6 and suggested using 0.2.6 for now, the 2 remaining Nitro version (0.2.7, 0.2.8) will under further investigation.
|
||||
- The team started testing on the fixed build.
|
||||
- _10 Jan, 3:22 pm SGT:_ Diagnosis found that it's most likely a false positive. @Hien has only found a solution by attempting to build Nitro Windows CPU on a GitHub-hosted runner and hasn't identified the root cause yet.
|
||||
- _10 Jan, 5:24 pm SGT:_ @Hien testing two scenarios and still trying to understand the workings of Bitdefender.
|
||||
- _11 Jan, 5:46 pm SGT:_ Postmortem meeting
|
||||
|
||||
## Investigation Update
|
||||
|
||||
- @Hien has investigated all versions of Nitro and conducted scans using Bitdefender. and only the 2 latest versions raised warnings from Bitdefender. Nitro 0.2.6, which is the highest version without the issue, was tested again, and it no longer triggers a warning from Bitdefender.
|
||||
- We have observed that Nitro versions up to 0.2.6 remain unaffected. However, Bitdefender flags versions 0.2.7 and 0.2.8 as infected, leading to the deletion. In order to proceed with the current release, Hien suggests downgrading Nitro to version 0.2.6 and conducting tests with this version. Simultaneously, he will investigate why Bitdefender is flagging versions 0.2.7 and 0.2.8.
|
||||
- It's essential to note that between versions 0.2.6, 0.2.7, and 0.2.8, only minor changes were made, which should not trigger a malicious code warning. We can refer to the changelog between 0.2.7 and 0.2.8 to pinpoint these changes.
|
||||
- Our primary message is to convey that we did not introduce malicious code into Jan (indicating a false positive), and the investigation aims to understand the root cause behind Bitdefender flagging versions 0.2.7 and 0.2.8.
|
||||
- The current diagnosis looks like a false positive but it's still under investigation. Reference link: [here](https://stackoverflow.com/questions/75886428/fake-positive-bit-defender-problem-genvariant-tedy-304469), [here](https://stackoverflow.com/questions/58010466/bitdefender-detects-my-console-application-as-genvariant-ursu-56053), and [here](https://www.cisa.gov/sites/default/files/2023-06/mar-10365227.r1.v1.clear_.pdf).
|
||||
- @Hien testing two scenarios and still trying to understand the workings of Bitdefender. Still under investigation: is the issue with the code or the CI?
|
||||
- In Case 1, using the same CI agent for tags 0.2.6 and 0.2.8, after PRs by Alan and myself, Bitdefender flagged the Nitro CPU binary build. Naturally, one would conclude this is due to the code.
|
||||
- However, I proceeded with a further experiment: for the 0.2.8 code, instead of using our CI agent, I used a GitHub hosted agent. This time, Bitdefender did not flag our binary build.
|
||||
- We've identified the Bitdefender warning was not an attack. There is no malicious code
|
||||
- We've isolated the event to originate from a CI agent, which resulted in a BitDefender false positive alert.
|
||||
|
||||
## Follow-ups and Action Items
|
||||
|
||||
1. **Reproduce Bitdefender Flag in Controlled Environment [Done]:**
|
||||
|
||||
- _Objective:_ To replicate the issue in a controlled environment to understand the triggers and specifics of Bitdefender's detection.
|
||||
|
||||
2. **Investigate Malicious Code or False Positive:**
|
||||
|
||||
- _Objective:_ Determine whether the flagged issue is a result of actual malicious code or a false positive. If it's a false positive, work towards resolution while communicating with Bitdefender.
|
||||
|
||||
3. **Supply Chain Attack Assessment:**
|
||||
|
||||
- _Objective:_ Evaluate the possibility of a supply chain attack. Investigate whether the Nitro 0.4.4 distribution was compromised or tampered with during the release process.
|
||||
|
||||
4. **Testing after the Hotfix:**
|
||||
|
||||
- _Objective:_ In addition to verifying the issue after the fix, it is essential to conduct comprehensive testing across related areas, ensuring compatibility across different operating systems and antivirus software (latest version / free version only).
|
||||
|
||||
5. **Process Improvement for Future Releases:**
|
||||
|
||||
- _Objective:_ Identify and implement improvements to our release process to prevent similar incidents in the future. This may include enhanced testing procedures, code analysis, and collaboration with antivirus software providers during the pre-release phase. Additionally, we should add verifying the latest antivirus software in the release checklist.
|
||||
|
||||
6. **Documentation of Tested Antivirus Versions:**
|
||||
- _Objective:_ Create a document that outlines the testing conducted, including a matrix that correlates Jan versions with the tested antivirus versions.
|
||||
- _Sample list:_ for consideration purpose
|
||||
- Bitdefender
|
||||
- McAfee
|
||||
- Avira
|
||||
- Kaspersky
|
||||
- Norton
|
||||
- Microsoft defender
|
||||
- AVG
|
||||
- TotalAV
|
||||
|
||||
## Next Steps
|
||||
|
||||
- The team should follow up on each action item with clear ownership priority, and deadlines.
|
||||
- Communicate progress transparently with the community and clients through appropriate channels. If any insights or suggestions, share them within the dedicated channels.
|
||||
- Update internal documentation and procedures based on the lessons learned from this incident.
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Antivirus Compatibility Awareness:**
|
||||
|
||||
- _Observation:_ The incident underscored the significance of recognizing and testing for antivirus compatibility, particularly with widely-used solutions like Bitdefender.
|
||||
- _Lesson Learned:_ In the future, we will integrate comprehensive checks for compatibility with various antivirus software, including both antivirus and "Malicious Code Detection," into our CI or QA checklist. This proactive measure aims to minimize false positive detections during the release and testing processes.
|
||||
|
||||
2. **Cross-Platform Testing:**
|
||||
|
||||
- _Observation:_ The problem did not occur on MacOS and Linux systems, implying a potential oversight in cross-platform testing during our release procedures.
|
||||
- _Lesson Learned:_ Clarification — This observation is not directly related to antivirus testing. Instead, it underscores the necessity to improve our testing protocols, encompassing multiple operating systems. This ensures a thorough evaluation of potential issues on diverse platforms, considering the various antivirus software and differences in architectures on Mac and Linux systems.
|
||||
|
||||
3. **User Communication and Documentation:**
|
||||
|
||||
- _Observation:_ Due to the timely response from Nicole, who was still active on Discord and Github at 2 am, this quick response facilitated our ability to assess the impact accurately.
|
||||
- _Lesson Learned:_ While our communication with users was effective in this instance, it was mainly due to Nicole's presence during the incident. To improve our overall response capability, we should prioritize "24/7 rapid triage and response." This involves ensuring continuous availability or establishing a reliable rotation of team members for swift user communication and issue documentation, further enhancing our incident response efficiency.
|
||||
|
||||
4. **Proactive Incident Response:**
|
||||
|
||||
- _Observation:_ The incident response, while involving a prompt version rollback, experienced a slight delay due to the release occurring at midnight. This delay postponed the initiation of the investigation until the next working hours.
|
||||
- _Lesson Learned:_ Recognizing the importance of swift incident response, particularly in time-sensitive situations, we acknowledge that releasing updates during off-hours can impact the immediacy of our actions. Moving forward, we will strive to optimize our release schedules to minimize delays and ensure that investigations can commence promptly regardless of the time of day. This may involve considering alternative release windows or implementing automated responses to critical incidents, ensuring a more proactive and timely resolution.
|
||||
|
||||
5. **Supply Chain Security Measures:**
|
||||
|
||||
- _Observation:_ While the incident prompted consideration of a potential supply chain attack, it's crucial to emphasize that this was not the case. Nonetheless, the incident underscored the importance of reviewing our supply chain security measures.
|
||||
- _Lesson Learned:_ Going forward, we should strengthen supply chain security by introducing additional verification steps to uphold the integrity of our release process. Collaborating with distribution channels is essential for enhancing security checks and ensuring a robust supply chain.
|
||||
- _Longer-term:_ Exploring options for checking Jan for malicious code and incorporating antivirus as part of our CI/CD pipeline should be considered for a more comprehensive and proactive approach.
|
||||
|
||||
6. **User Education on False Positives:**
|
||||
- _Observation:_ Users reported Bitdefender automatically "disinfecting" the flagged Nitro version without allowing any user actions.
|
||||
- _Lesson Learned:_ Educate users about the possibility of false positives and guide them on how to whitelist or report such incidents to their antivirus provider (if possible). Provide clear communication on steps users can take in such situations.
|
||||
|
||||
These lessons learned will serve as a foundation for refining our processes and ensuring a more resilient release and incident response framework in the future. Continuous improvement is key to maintaining the reliability and security of our software.
|
||||
|
||||
Thank you for your dedication and cooperation in resolving this matter promptly.
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: What We Do
|
||||
slug: /handbook/what-we-do
|
||||
title: Postmortems
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /postmortems
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -12,10 +12,9 @@ keywords:
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
<DocCardList />
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Our Products and Innovations
|
||||
slug: /handbook/products-and-innovations
|
||||
title: Engineering
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /engineering
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
@ -12,10 +12,10 @@ keywords:
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
## Prerequisites
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
- [Requirements](https://github.com/janhq/jan?tab=readme-ov-file#requirements-for-running-jan)
|
||||
- [Setting up local env](https://github.com/janhq/jan?tab=readme-ov-file#contributing)
|
||||
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
@ -1,8 +1,9 @@
|
||||
---
|
||||
title: Product
|
||||
title: Project Management
|
||||
slug: /product/management
|
||||
---
|
||||
|
||||
We use the [Jan Monorepo Project](https://github.com/orgs/janhq/projects/5) in Github for 100% of our product / project management.
|
||||
We use the [Jan Monorepo Project](https://github.com/orgs/janhq/projects/5) in Github to manage our roadmap and sprint Kanbans.
|
||||
|
||||
As much as possible, everyone owns their respective `epics` and `tasks`.
|
||||
|
||||
9
docs/docs/about/04-product/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Product
|
||||
slug: /product
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Figma](https://figma.com)
|
||||
- [ScreenStudio](https://www.screen.studio/)
|
||||
31
docs/docs/about/05-community/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Community
|
||||
slug: /community
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
]
|
||||
---
|
||||
|
||||
## Socials
|
||||
|
||||
- [Discord](https://discord.gg/SH3DGmUs6b)
|
||||
- [X](https://twitter.com/janframework)
|
||||
- [HuggingFace](https://huggingface.co/janhq)
|
||||
- [LinkedIn](https://www.linkedin.com/company/janframework/)
|
||||
|
||||
## Community Run
|
||||
|
||||
- [Reddit](https://www.reddit.com/r/janframework/)
|
||||
|
||||
## Careers
|
||||
|
||||
- [Jobs](https://janai.bamboohr.com/careers)
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Meet Jan
|
||||
slug: /handbook/meet-jan
|
||||
title: Events
|
||||
slug: /events
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
@ -12,10 +12,9 @@ keywords:
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
<DocCardList />
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Our Contributors
|
||||
slug: /handbook/core-contributors
|
||||
title: Careers
|
||||
slug: /careers
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
@ -12,10 +12,9 @@ keywords:
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
## We're hiring
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
[Careers on Bamboo](https://janai.bamboohr.com/careers)
|
||||
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Community
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords: [Jan AI, Jan, ChatGPT alternative, local AI, private AI, conversational AI, no-subscription fee, large language model ]
|
||||
---
|
||||
|
||||
- [ ] Social media links
|
||||
@ -20,3 +20,69 @@ keywords:
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
In this guide, we'll walk you through the process of building your first extension and integrating it into Jan.
|
||||
|
||||
## Steps to Create Your First Extension
|
||||
|
||||
To create your own extension, you can follow the steps below:
|
||||
|
||||
1. Click the **Use this template** button at the top of the [extension-template repository](https://github.com/janhq/extension-template).
|
||||
2. Select **Create a new repository**.
|
||||
3. Choose an owner and name for your new repository.
|
||||
4. Click **Create repository**.
|
||||
5. Clone your new repository to your local machine.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
After you have cloned the repository to your local machine or codespace, you will need to perform some initial setup steps before you can develop your extension.
|
||||
|
||||
:::info
|
||||
|
||||
You will need to have a reasonably modern version of [Node.js](https://nodejs.org) handy. If you are using a version manager like [`nodenv`](https://github.com/nodenv/nodenv) or [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the root of your repository to install the version specified in
|
||||
[`package.json`](https://github.com/janhq/extension-template/blob/main/package.json). Otherwise, 20.x or later should work!
|
||||
|
||||
:::
|
||||
|
||||
1. :hammer_and_wrench: Install the dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. :building_construction: Package the TypeScript for distribution
|
||||
|
||||
```bash
|
||||
npm run bundle
|
||||
```
|
||||
|
||||
3. :white_check_mark: Check your artifact
|
||||
|
||||
There will be a `.tgz` file in your extension directory now. This is the file you will need to import into Jan. You can import this file into Jan by following the instructions in the [Import Extension](https://jan.ai/guides/using-extensions/import-extensions/) guide.
|
||||
|
||||
## Update the Extension Metadata
|
||||
|
||||
The [`package.json`](https://github.com/janhq/extension-template/blob/main/package.json) file defines metadata about your extension, such as extension name, main entry, description and version.
|
||||
|
||||
When you copy this repository, update `package.json` with the name, and description for your extension.
|
||||
|
||||
## Update the Extension Code
|
||||
|
||||
The [`src/`](https://github.com/janhq/extension-template/tree/main/src) directory is the heart of your extension! This contains the source code that will be run when your extension extension functions are invoked. You can replace the contents of this directory with your own code.
|
||||
|
||||
There are a few things to keep in mind when writing your extension code:
|
||||
|
||||
- Most Jan Extension functions are processed asynchronously.
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Extension Core module, see the [documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
Now, go ahead and start customizing your extension! Happy coding!
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Hardware Requirements
|
||||
slug: /guides/install/hardware
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Antivirus Compatibility Testing
|
||||
slug: /guides/install/antivirus-compatibility-testing
|
||||
description: Antivirus compatibility testing documentation for the Jan App v0.4.4 release.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
antivirus compatibility,
|
||||
]
|
||||
---
|
||||
|
||||
This documentation outlines the antivirus compatibility testing conducted for the Jan App v0.4.4 release. This documentation includes a matrix that correlates the Jan App version with the tested antivirus versions.
|
||||
|
||||
## Tested Antivirus Versions
|
||||
|
||||
The Jan App v0.4.4 release has undergone automatic testing through CI with a selection of popular antivirus software to ensure compatibility and safety. The following summarizes the testing results:
|
||||
|
||||
| Antivirus | Version | Result |
|
||||
| ------------------ | ------------ | -------------------------------- |
|
||||
| Bitdefender | 27.0.27.125 | Scanned and 0 threat(s) detected |
|
||||
| McAfee | 4.21.0.0 | Scanned and 0 threat(s) detected |
|
||||
| Microsoft Defender | 1.403.2259.0 | Scanned and 0 threat(s) detected |
|
||||
|
||||
## Conclusion
|
||||
|
||||
The testing indicates that Jan App v0.4.4 is compatible with Bitdefender, Microsoft Defender, and McAfee. Any updates or changes to compatibility status will be promptly documented.
|
||||
133
docs/docs/guides/06-using-extensions/01-extension-settings.md
Normal file
@ -0,0 +1,133 @@
|
||||
---
|
||||
title: Extension Settings
|
||||
slug: /guides/using-extensions/extension-settings/
|
||||
description: Configure settings for extensions.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
extension settings,
|
||||
]
|
||||
---
|
||||
|
||||
The current Jan Desktop Client has some default extensions built on top of this framework to enhance the user experience. In this guide, we will show you the list of default extensions and how to configure extension settings.
|
||||
|
||||
## Default Extensions
|
||||
|
||||
You can find the default extensions in the `Settings` > `Extensions`.
|
||||
|
||||

|
||||
|
||||
### List of Default Extensions
|
||||
|
||||
| Extension Name | Version | Description | Source Code Link |
|
||||
| ---------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| Assistant Extension | v1.0.0 | This extension enables assistants, including Jan, a default assistant that can call all downloaded models. | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/assistant-extension) |
|
||||
| Conversational Extension | v1.0.0 | This extension enables conversations and state persistence via your filesystem. | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/conversational-extension) |
|
||||
| Inference Nitro Extension | v1.0.0 | This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See [nitro.jan.ai](nitro.jan.ai) | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/inference-nitro-extension) |
|
||||
| Inference Openai Extension | v1.0.0 | This extension enables OpenAI chat completion API calls | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/inference-openai-extension) |
|
||||
| Inference Triton Trt Llm Extension | v1.0.0 | This extension enables Nvidia's TensorRT-LLM as an inference engine option. | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/inference-triton-trtllm-extension) |
|
||||
| Model Extension | v1.0.22 | Model Management Extension provides model exploration and seamless downloads. | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/model-extension) |
|
||||
| Monitoring Extension | v1.0.9 | This extension provides system health and OS level data. | [Link to Source](https://github.com/janhq/jan/tree/main/extensions/monitoring-extension) |
|
||||
|
||||
## Configure Extension Settings
|
||||
|
||||
You can configure the extension settings by modifying the `extensions.json` file under the `~/jan/extensions` directory including the following configurations:
|
||||
|
||||
- `_active`: true means the extension is enabled. If you want to disable an extension, you can set it to false.
|
||||
- `listeners`: {} is the default value for listeners.
|
||||
- `origin`: the path to the extension file.
|
||||
- `installOptions`: configure the installOptions with version and fullMetadata.
|
||||
- `name`: the name of the extension.
|
||||
- `version`: the version of the extension.
|
||||
- `main`: the path to the main file of the extension.
|
||||
- `description`: the description of the extension.
|
||||
- `url`: the url of the extension.
|
||||
|
||||
```json title="~/jan/extensions/extensions.json"
|
||||
{
|
||||
"@janhq/assistant-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-assistant-extension-1.0.0.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/assistant-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
|
||||
"url": "extension://@janhq/assistant-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/conversational-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-conversational-extension-1.0.0.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/conversational-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension enables conversations and state persistence via your filesystem",
|
||||
"url": "extension://@janhq/conversational-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/inference-nitro-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-inference-nitro-extension-1.0.0.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/inference-nitro-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See nitro.jan.ai",
|
||||
"url": "extension://@janhq/inference-nitro-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/inference-openai-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-inference-openai-extension-1.0.0.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/inference-openai-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension enables OpenAI chat completion API calls",
|
||||
"url": "extension://@janhq/inference-openai-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/inference-triton-trt-llm-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-inference-triton-trt-llm-extension-1.0.0.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/inference-triton-trt-llm-extension",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension enables Nvidia's TensorRT-LLM as an inference engine option",
|
||||
"url": "extension://@janhq/inference-triton-trt-llm-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/model-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-model-extension-1.0.22.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/model-extension",
|
||||
"version": "1.0.22",
|
||||
"main": "dist/index.js",
|
||||
"description": "Model Management Extension provides model exploration and seamless downloads",
|
||||
"url": "extension://@janhq/model-extension/dist/index.js"
|
||||
},
|
||||
"@janhq/monitoring-extension": {
|
||||
"_active": true,
|
||||
"listeners": {},
|
||||
"origin": "/Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-monitoring-extension-1.0.9.tgz",
|
||||
"installOptions": { "version": false, "fullMetadata": false },
|
||||
"name": "@janhq/monitoring-extension",
|
||||
"version": "1.0.9",
|
||||
"main": "dist/index.js",
|
||||
"description": "This extension provides system health and OS level data",
|
||||
"url": "extension://@janhq/monitoring-extension/dist/index.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Import Extensions
|
||||
slug: /guides/using-extensions/import-extensions/
|
||||
description: Import extensions into Jan.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
using-models,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Extension Settings
|
||||
slug: /guides/using-extensions/extension-settings/
|
||||
description: Configure settings for extensions.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
using-models,
|
||||
]
|
||||
---
|
||||
29
docs/docs/guides/06-using-extensions/02-import-extensions.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Import Extensions
|
||||
slug: /guides/using-extensions/import-extensions/
|
||||
description: Import extensions into Jan.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
import extensions,
|
||||
]
|
||||
---
|
||||
|
||||
Beside default extensions, you can import extensions into Jan by navigate to `Settings` > `Extensions` > `Manual Installation`. Then, the `~/jan/extensions/extensions.json` file will be updated automatically.
|
||||
|
||||
:::caution
|
||||
|
||||
You need to prepare the extension file in `.tgz` format to install.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
If you want to build your own extension, please refer to the [Build Your First Extension | Developer Documentation](/developer/build-extension/your-first-extension/).
|
||||
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 17 MiB |
@ -37,7 +37,7 @@ You can find your API keys in the [OpenRouter API Key](https://openrouter.ai/key
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Mofidy a Model JSON
|
||||
### 2. Modify a Model JSON
|
||||
|
||||
Navigate to the `~/jan/models` folder. Create a folder named `<openrouter-modelname>`, for example, `openrouter-dolphin-mixtral-8x7b` and create a `model.json` file inside the folder including the following configurations:
|
||||
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Integrate Azure OpenAI Service with Jan
|
||||
slug: /guides/integrations/azure-openai-service
|
||||
description: Guide to integrate Azure OpenAI Service with Jan
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
integration,
|
||||
Azure OpenAI Service,
|
||||
]
|
||||
---
|
||||
|
||||
## Quick Introduction
|
||||
|
||||
[Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview?source=docs) provides a set of powerful APIs that enable you to easily integrate the OpenAI's language models.
|
||||
|
||||
In this guide, we will show you how to integrate Azure OpenAI Service with Jan.
|
||||
|
||||
## Steps to Integrate Azure OpenAI Service with Jan
|
||||
|
||||
### 1. Configure Azure OpenAI Service API key
|
||||
|
||||
Once you completed setting up and deploying the Azure OpenAI Service, you can find the endpoint and API key in the [Azure OpenAI Studio](https://oai.azure.com/) by navigating to `Chat` > `View code`.
|
||||
|
||||

|
||||
|
||||
<br> </br>
|
||||
|
||||

|
||||
|
||||
Set the Azure OpenAI Service endpoint and API key in the `~/jan/engines/openai.json` file.
|
||||
|
||||
```json title="~/jan/engines/openai.json"
|
||||
{
|
||||
// https://hieujan.openai.azure.com/openai/deployments/gpt-35-hieu-jan/chat/completions?api-version=2023-07-01-preview
|
||||
// highlight-start
|
||||
"full_url": "https://<your-resource-name>.openai.azure.com/openai/deployments/<your-deployment-name>/chat/completions?api-version=<api-version>",
|
||||
"api_key": "<your-api-key>"
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Modify a Model JSON
|
||||
|
||||
Navigate to the `~/jan/models` folder. Create a folder named `<your-deployment-name>`, for example, `gpt-35-hieu-jan` and create a `model.json` file inside the folder including the following configurations:
|
||||
|
||||
- Ensure the filename must be `model.json`.
|
||||
- Ensure the `id` property is set to the same as the folder name and your deployment name.
|
||||
- Ensure the `format` property is set to `api`.
|
||||
- Ensure the `engine` property is set to `openai`.
|
||||
- Ensure the `state` property is set to `ready`.
|
||||
|
||||
```json title="~/jan/models/gpt-35-hieu-jan/model.json"
|
||||
{
|
||||
"source_url": "https://hieujan.openai.azure.com",
|
||||
// highlight-next-line
|
||||
"id": "gpt-35-hieu-jan",
|
||||
"object": "model",
|
||||
"name": "Azure OpenAI GPT 3.5",
|
||||
"version": "1.0",
|
||||
"description": "Azure Open AI GPT 3.5 model is extremely good",
|
||||
// highlight-next-line
|
||||
"format": "api",
|
||||
"settings": {},
|
||||
"parameters": {},
|
||||
"metadata": {
|
||||
"author": "OpenAI",
|
||||
"tags": ["General", "Big Context Length"]
|
||||
},
|
||||
// highlight-start
|
||||
"engine": "openai",
|
||||
"state": "ready"
|
||||
// highlight-end
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Start the Model
|
||||
|
||||
Restart Jan and navigate to the Hub. Locate your model and click the Use button.
|
||||

|
||||
|
||||
### 4. Try Out the Integration of Jan and Azure OpenAI Service
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 827 KiB |
|
After Width: | Height: | Size: 9.9 MiB |
BIN
docs/docs/guides/07-integrations/assets/03-start-model.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/docs/guides/07-integrations/assets/03-viewcode.png
Normal file
|
After Width: | Height: | Size: 567 KiB |
@ -1,8 +1,11 @@
|
||||
---
|
||||
title: Something's amiss
|
||||
title: Something's Amiss
|
||||
slug: /troubleshooting/somethings-amiss
|
||||
description: Troubleshooting "Something's amiss".
|
||||
keywords: [
|
||||
jan ai failed to fetch,
|
||||
failed to fetch error,
|
||||
jan ai error,
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
@ -16,6 +19,12 @@ keywords: [
|
||||
]
|
||||
---
|
||||
|
||||
{/* Imports */}
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
Previously labelled "Failed to fetch" error.
|
||||
|
||||
You may receive a "Something's amiss" response when you first start chatting with a selected model.
|
||||
|
||||
This may occur due to several reasons. Please follow these steps to resolve it:
|
||||
@ -30,8 +39,45 @@ This may occur due to several reasons. Please follow these steps to resolve it:
|
||||
|
||||
3. Install the latest [Nightly release](https://jan.ai/install/nightly/)
|
||||
|
||||
- If you are re-installing Jan, it can help to [clear the application cache](https://jan.ai/troubleshooting/stuck-on-broken-build/)
|
||||
- If you are re-installing Jan, it can help to [clear the application cache](https://jan.ai/troubleshooting/stuck-on-broken-build/).
|
||||
|
||||
4. Ensure your V/RAM is accessible by the application (some people have virtual RAM)
|
||||
4. Ensure your V/RAM is accessible by the application (some people have virtual RAM).
|
||||
|
||||
5. If you are on Nvidia GPUs, please download [Cuda](https://developer.nvidia.com/cuda-downloads)
|
||||
5. If you are on Nvidia GPUs, please download [Cuda](https://developer.nvidia.com/cuda-downloads).
|
||||
|
||||
6. When [checking app logs](https://jan.ai/troubleshooting/how-to-get-error-logs/), if you encounter the error log `Bind address failed at 127.0.0.1:3928`, it indicates that the port used by Nitro might already be in use. Use the following commands to check the port status:
|
||||
|
||||
<Tabs groupId="operating-systems">
|
||||
<TabItem value="mac" label="macOS">
|
||||
|
||||
```bash
|
||||
netstat -an | grep 3928
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="win" label="Windows">
|
||||
|
||||
```sh
|
||||
netstat -ano | find "3928"
|
||||
tasklist /fi "PID eq 3928"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
|
||||
```sh
|
||||
netstat -anpe | grep "3928"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
|
||||
Jan uses the following ports:
|
||||
|
||||
- Nitro: 3928
|
||||
- Jan API Server: 1337
|
||||
- Jan Documentation: 3001
|
||||
|
||||
:::
|
||||
|
||||
32
docs/docs/guides/08-troubleshooting/05-permission-denied.mdx
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Permission Denied
|
||||
slug: /troubleshooting/permission-denied
|
||||
description: Permission denied.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
troubleshooting,
|
||||
permission denied,
|
||||
]
|
||||
---
|
||||
|
||||
When you run Jan, you may encounter the following error:
|
||||
|
||||
```bash
|
||||
Uncaught (in promise) Error: Error invoking layout-480796bff433a3a3.js:538 remote method 'installExtension':
|
||||
Error Package /Applications/Jan.app/Contents/Resources/app.asar.unpacked/pre-install/janhq-assistant-extension-1.0.0.tgz does not contain a valid manifest:
|
||||
Error EACCES: permission denied, mkdtemp '/Users/username/.npm/_cacache/tmp/ueCMn4'
|
||||
```
|
||||
|
||||
This error indicates a permission issue during the installation process. To fix this issue, you can run the following command to change ownership of the `~/.npm` directory to the current user:
|
||||
|
||||
```bash
|
||||
sudo chown -R $(whoami) ~/.npm
|
||||
```
|
||||
20
docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Unexpected Token
|
||||
slug: /troubleshooting/unexpected-token
|
||||
description: Unexpected token is not a valid JSON
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
troubleshooting,
|
||||
unexpected token,
|
||||
]
|
||||
---
|
||||
|
||||
1. You may receive an error response `Error occurred: Unexpected token '<', "<!DOCTYPE"...is not valid JSON`, when you start a chat with OpenAI models. Using a VPN may help fix the issue.
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Why we exist
|
||||
slug: /handbook/meet-jan/why-we-exist
|
||||
description: Why we exist
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Vision and Mission
|
||||
slug: /handbook/meet-jan/vision-and-mission
|
||||
description: Vision and mission of Jan
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Overview of Jan Framework and Its Applications
|
||||
slug: /handbook/products-and-innovations/overview-of-jan-framework-and-its-applications
|
||||
description: Overview of Jan Framework and Its Applications
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Philosophy Behind Product Development
|
||||
slug: /handbook/products-and-innovations/philosophy-behind-product-development
|
||||
description: Philosophy Behind Product Development
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Roadmap - Present and Future Directions
|
||||
slug: /handbook/products-and-innovations/roadmap-present-and-future-directions
|
||||
description: Roadmap - Present and Future Directions
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,90 +0,0 @@
|
||||
---
|
||||
title: How We Hire at Jan
|
||||
slug: /handbook/core-contributors/how-we-hire
|
||||
description: See how open-source LLM AI company Jan.ai (maker of Jan framework) approaches team building and hiring.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
how-we-hire,
|
||||
]
|
||||
---
|
||||
|
||||
_The purpose - To conduct fair, unbiased interviews that focus on identifying candidates with initiative, scrappiness, and a good fit for a core contributor role at Jan._
|
||||
|
||||
## Our Key Principles
|
||||
|
||||
### Avoiding Bias
|
||||
|
||||
Tempting as it is to judge someone based on the fact that they _also_ binge-watched Severance, every role at Jan is a key hire and should be interviewed to the same standard.
|
||||
|
||||
What does fair interviewing look like at Jan?
|
||||
At Jan, the team assesses candidates based on their skills, experiences, and fit for the role and company culture. We expect an in turn assessment and healthy curiosity.
|
||||
|
||||
We’re striving for a middle ground between hiring exclusively within our network (like PayPal in the early days) and broader, more open hiring practices that reduce bias.
|
||||
|
||||
[Learn more](./assets/01-learn-more.png)
|
||||
|
||||
What the candidate is looking for is just as important as what we at Jan are looking for.
|
||||
|
||||
Value alignment isn’t something that’s often spoken about when hiring for a new role. At Jan, we believe that value alignment is the stuff folks really mean when they say “culture fit”.
|
||||
|
||||

|
||||
|
||||
## We Care About
|
||||
|
||||
### Initiative and Scrappiness
|
||||
|
||||
We want people on the team who demonstrate resourcefulness, problem-solving skills, and the ability to thrive in a bootstrapped, open-source environment. (This isn’t a mandated “do more with less!” statement, but rather, we appreciate those who _get_ where we’re coming from.)
|
||||
|
||||
### Diversity, Equity, and Inclusion (DEI)
|
||||
|
||||
While we don't have a large DEI program, we are committed to inclusivity and respect for diverse opinions and backgrounds. Our focus is on individual merit and cultural fit, rather than ticking DEI checkboxes.
|
||||
|
||||
### Values
|
||||
|
||||
The stuff that’s hard to put into words but you can always tell when it’s missing in your company. Our values play out in our everyday behaviors.
|
||||
|
||||
## The Kinds of Questions You Could Expect From an Interview
|
||||
|
||||
- Can you describe a time when you had to be resourceful or "scrappy" to solve a problem? What was the outcome?
|
||||
- How do you stay motivated and productive in environments with limited resources or guidance?
|
||||
- Can you give an example of a successful project you contributed to in an open-source community?
|
||||
- How do you handle disagreements or differing opinions in a professional setting?
|
||||
- Describe a situation where you took the initiative in a project or task. What drove you to take charge?
|
||||
- In your view, what's the most challenging aspect of working in a bootstrapped company, and how would you navigate this challenge?
|
||||
- How do you prioritize tasks?
|
||||
- Can you discuss a time when you had to learn a new skill or technology quickly to complete a task? How did you approach this?
|
||||
- What does 'cultural fit' mean to you, and why do you think it's important in a workplace?
|
||||
- How do you balance the need for collaboration with independent work in your professional life?
|
||||
|
||||
## The (Ideal) Process
|
||||
|
||||
### Pre-Interview
|
||||
|
||||
Review the candidate's resume and portfolio without preconceived notions. Focus on their skills and experiences relevant to the role.
|
||||
|
||||
### During the Interview (internal notes)
|
||||
|
||||
Use the questions as a guide but be flexible to explore interesting points raised by the candidate. Ask the same questions of everyone if possible.
|
||||
|
||||
Observe not just what they say, but how they think and approach problems.
|
||||
|
||||
### Post-Interview
|
||||
|
||||
Evaluate candidates based on their responses, skills, potential cultural fit, and ability to contribute to Jan's goals within their proposed function. Avoid making snap judgments based on first impressions or personal biases.
|
||||
|
||||
### Decision Making
|
||||
|
||||
Combine feedback from everyone involved in hiring. Consider how the new hire would engage with others in their pod and the wider team.
|
||||
|
||||
Hiring isn’t easy, and at Jan, we’re aiming to be as transparent and inclusive as we can, with the knowledge that as a small core contributor team, we need to be extremely selective about who we onboard.
|
||||
|
||||
The intention is they’re here for the long haul!
|
||||
@ -1,58 +0,0 @@
|
||||
---
|
||||
title: Embracing Pod Structure
|
||||
slug: /handbook/core-contributors/embracing-pod-structure
|
||||
description: How we move fast in engineering and cross-departments by owning functions, clear prioritization and sprints.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
embracing pod structure,
|
||||
]
|
||||
---
|
||||
|
||||
How do you operate fully remotely? How do you run an open source company without everyone going ape? This post aims to answer questions the Jan team answers regularly in Discord.
|
||||
|
||||
### Small Pods = Big Impact
|
||||
|
||||
At Jan, we operate in small pods- like nerdy dolphins. These are compact, agile teams, each taking ownership of specific aspects of our product. This structure allows us to ship features at an impressive speed, maintain a clear focus on our business goals, and experience minimal roadblocks.
|
||||
|
||||
### Our Approach to the Jan Roadmap
|
||||
|
||||
How do we decide what gets prioritized when there are so many potentials?
|
||||
|
||||

|
||||
|
||||
[Our product handbook](https://jan.ai/handbook/product/) is not just a document; it makes up part of our larger open company handbook. The aim is for everything to be outlined - from our high-level roadmap, which is used for strategic alignment and reflects our engineering implementation cycles, to our Standup Kanban, the go-to view for our daily standups.
|
||||
|
||||
### How We Label
|
||||
|
||||
We use Roadmap Labels to tag large, long-term projects that often span multiple teams and sprints. For example, a label like 'roadmap: Jan has Mobile' signifies a significant strategic initiative.
|
||||
|
||||
These roadmaps contain epics – large stories that typically span 1-2 weeks, complete with specs, architecture decisions, and designs. Each epic has one owner, ensuring clear responsibility and accountability.
|
||||
|
||||
### Milestones & Tasks
|
||||
|
||||
Our milestones, aligned with semantic versioning, track release versions and fit neatly into our 2-week sprint cycles. Tasks, whether they're features, bugs, or chores, are designed to be completed within a few days. They always belong to an epic, tying back to our larger roadmap.
|
||||
|
||||
### Kanban - Because a Cluttered Brain Doesn’t Make Decisions
|
||||
|
||||
Our Kanban board is a dynamic snapshot of our workflow. It ranges from 'no status' for issues needing triage, to 'done' for completed tasks. This system helps us efficiently manage our workflow and ensures that every team member is aware of their responsibilities and the current state of our projects.
|
||||
|
||||
### How We Triage Bugs
|
||||
|
||||
Our Triage Standard Operating Procedure (SOP) is crucial for handling urgent bugs by assigning them to an owner and tagging them to the current sprint and milestone. For all other tasks, we assign the correct roadmap label(s) and owner. Determining what fires we let burn is a continuous learning and practice to build.
|
||||
|
||||
### Our Rhythm- Standups, sprints, demos, and TGIFs
|
||||
|
||||
Our day kicks off with a standup at 11 AM, where we align our tasks and address any immediate concerns. We run in sprints, which keeps us focused and fast. Fridays are special at Jan – not just because it's the end of the week, but because we have demos and TGIF sessions. Not just for kicking back with questionable Google Meet filters but also for celebrating our achievements and learning from our experiences.
|
||||
|
||||
### Small Pods, Big Dreams
|
||||
|
||||
At Jan our small pod structure is more than just an organizational choice; it's a reflection of our belief in empowerment, agility, and clear focus. Hierarchical management and top-down decision making are something that goes against our core - and we’ve found that by being small and owning functions - our objectives aren’t just goals - they’re becoming reality.
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: The Art of Conflict
|
||||
slug: /handbook/core-contributors/the-art-of-conflict
|
||||
description: The Art of Conflict
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: OpSec
|
||||
slug: /handbook/core-contributors/opsec
|
||||
description: OpSec
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: See a Problem, Own a Problem
|
||||
slug: /handbook/core-contributors/see-a-problem-own-a-problem
|
||||
description: See a Problem, Own a Problem - How we function without management
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
|
Before Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 135 KiB |
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Our Support Methodology - Open Source, Collaborative, and Self-serve
|
||||
slug: /handbook/what-we-do/our-support-methodology
|
||||
description: Our Support Methodology - Open Source, Collaborative, and Self-serve
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Our Approach to Design
|
||||
slug: /handbook/what-we-do/our-approach-to-design
|
||||
description: Our Approach to Design
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Shipping Now, Shipping Later
|
||||
slug: /handbook/what-we-do/shipping-now-shipping-later
|
||||
description: Shipping Now, Shipping Later
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: Trial by Fire
|
||||
slug: /handbook/what-we-do/trial-by-fire
|
||||
description: Trial by Fire
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
handbook,
|
||||
]
|
||||
---
|
||||