From 0f8deddcdfac92913ac6a9682d16396e8b0a7692 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:27:13 +0900 Subject: [PATCH 01/98] docs: add requirements to installation guides --- docs/docs/guides/02-installation/01-mac.md | 4 ++++ .../docs/guides/02-installation/02-windows.md | 21 ++++++++++--------- docs/docs/guides/02-installation/03-linux.md | 17 +++++++++++++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/docs/guides/02-installation/01-mac.md b/docs/docs/guides/02-installation/01-mac.md index 8e67b5bed..a719cd913 100644 --- a/docs/docs/guides/02-installation/01-mac.md +++ b/docs/docs/guides/02-installation/01-mac.md @@ -17,6 +17,10 @@ keywords: # Installing Jan on MacOS +## Requirements + +Ensure that your MacOS version is 13 or higher to run Jan. + ## Installation Jan is available for download via our homepage, [https://jan.ai/](https://jan.ai/). diff --git a/docs/docs/guides/02-installation/02-windows.md b/docs/docs/guides/02-installation/02-windows.md index b200554d2..6623fba62 100644 --- a/docs/docs/guides/02-installation/02-windows.md +++ b/docs/docs/guides/02-installation/02-windows.md @@ -17,6 +17,17 @@ keywords: # Installing Jan on Windows +## System Requirements + +Ensure that your system meets the following requirements: + +- Windows 10 or higher is required to run Jan. + +To enable GPU support, you will need: + +- NVIDIA GPU with CUDA Toolkit 11.7 or higher +- NVIDIA driver 470.63.01 or higher + ## Installation Jan is available for download via our homepage, [https://jan.ai](https://jan.ai/). @@ -59,13 +70,3 @@ To remove all user data associated with Jan, you can delete the `/jan` directory cd C:\Users\%USERNAME%\AppData\Roaming rmdir /S jan ``` - -## Troubleshooting - -### Microsoft Defender - -**Error: "Microsoft Defender SmartScreen prevented an unrecognized app from starting"** - -Windows Defender may display the above warning when running the Jan Installer, as a standard security measure. - -To proceed, select the "More info" option and select the "Run Anyway" option to continue with the installation. diff --git a/docs/docs/guides/02-installation/03-linux.md b/docs/docs/guides/02-installation/03-linux.md index 21dfac1a9..bb93a8a3b 100644 --- a/docs/docs/guides/02-installation/03-linux.md +++ b/docs/docs/guides/02-installation/03-linux.md @@ -17,6 +17,18 @@ keywords: # Installing Jan on Linux +## Requirements + +Ensure that your system meets the following requirements: + +- glibc 2.27 or higher (check with `ldd --version`) +- gcc 11, g++ 11, cpp 11, or higher, refer to this [link](https://jan.ai/guides/troubleshooting/gpu-not-used/#specific-requirements-for-linux) for more information. + +To enable GPU support, you will need: + +- NVIDIA GPU with CUDA Toolkit 11.7 or higher +- NVIDIA driver 470.63.01 or higher + ## Installation Jan is available for download via our homepage, [https://jan.ai](https://jan.ai/). @@ -66,7 +78,8 @@ jan-linux-amd64-{version}.deb # AppImage jan-linux-x86_64-{version}.AppImage ``` -``` + +```` ## Uninstall Jan @@ -75,7 +88,7 @@ To uninstall Jan on Linux, you should use your package manager's uninstall or re ```bash sudo apt-get remove jan # where jan is the name of Jan package -``` +```` For other Linux distributions, if you installed Jan via the `.AppImage` file, you can uninstall Jan by deleting the `.AppImage` file. From cef149a11b3cd29c4285793b2e02ca60dfb47894 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:33:18 +0900 Subject: [PATCH 02/98] docs: finalize installation guide --- docs/docs/guides/02-installation/01-mac.md | 3 ++- docs/docs/guides/02-installation/02-windows.md | 1 + docs/docs/guides/02-installation/03-linux.md | 7 +++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/docs/guides/02-installation/01-mac.md b/docs/docs/guides/02-installation/01-mac.md index a719cd913..7a3961384 100644 --- a/docs/docs/guides/02-installation/01-mac.md +++ b/docs/docs/guides/02-installation/01-mac.md @@ -12,12 +12,13 @@ keywords: conversational AI, no-subscription fee, large language model, + installation guide, ] --- # Installing Jan on MacOS -## Requirements +## System Requirements Ensure that your MacOS version is 13 or higher to run Jan. diff --git a/docs/docs/guides/02-installation/02-windows.md b/docs/docs/guides/02-installation/02-windows.md index 6623fba62..d60ab86f7 100644 --- a/docs/docs/guides/02-installation/02-windows.md +++ b/docs/docs/guides/02-installation/02-windows.md @@ -12,6 +12,7 @@ keywords: conversational AI, no-subscription fee, large language model, + installation guide, ] --- diff --git a/docs/docs/guides/02-installation/03-linux.md b/docs/docs/guides/02-installation/03-linux.md index bb93a8a3b..0ec7fea60 100644 --- a/docs/docs/guides/02-installation/03-linux.md +++ b/docs/docs/guides/02-installation/03-linux.md @@ -12,12 +12,13 @@ keywords: conversational AI, no-subscription fee, large language model, + installation guide, ] --- # Installing Jan on Linux -## Requirements +## System Requirements Ensure that your system meets the following requirements: @@ -79,8 +80,6 @@ jan-linux-amd64-{version}.deb jan-linux-x86_64-{version}.AppImage ``` -```` - ## Uninstall Jan To uninstall Jan on Linux, you should use your package manager's uninstall or remove option. For Debian/Ubuntu-based distributions, if you installed Jan via the `.deb` package, you can uninstall Jan using the following command: @@ -88,7 +87,7 @@ To uninstall Jan on Linux, you should use your package manager's uninstall or re ```bash sudo apt-get remove jan # where jan is the name of Jan package -```` +``` For other Linux distributions, if you installed Jan via the `.AppImage` file, you can uninstall Jan by deleting the `.AppImage` file. From f23e9115fc5cce5e2a85e2bbc5b5392bad876f84 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 01:22:36 +0900 Subject: [PATCH 03/98] docs: add developer/install-and-prerequisites --- .../04-install-and-prerequisites.md | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/docs/developer/01-overview/04-install-and-prerequisites.md diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md new file mode 100644 index 000000000..dbc39fccd --- /dev/null +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -0,0 +1,65 @@ +--- +title: Installation and Prerequisites +slug: /developer/install-and-prerequisites +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, + installation, + prerequisites, + developer setup, + ] +--- + +## Requirements: + +- [Hardware Requirements](../../guides/02-installation/06-hardware.md) + +- System Requirements: + - [Windows](../../install/windows/#system-requirements) + - [MacOS](../../install/mac/#system-requirements) + - [Linux](../../install/linux/#system-requirements) + +## Prerequisites + +Before installing Jan, make sure you have the following installed on your computer: + +- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher) +- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher) +- [make](https://www.gnu.org/software/make/) (version 3.81 or higher) + +## Instructions + +1. Clone the repository and install dependencies + +```bash +git clone https://github.com/janhq/jan +cd jan +git checkout -b DESIRED_BRANCH +yarn install +``` + +2. Run development and use Jan Desktop + +```bash +make dev +``` + +This will start the development server and open the Jan Desktop app. + +## For Production Build + +```bash +# Do steps 1 and 2 in the previous section +# Build the app +make build +``` + +This will build the app MacOS (M1/M2/M3) for production (with code signing already done) and put the result in dist folder. From 7c0c6924d831bf6d070e8aa749ec8607dfeca6b9 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 02:28:37 +0900 Subject: [PATCH 04/98] docs: finalize-installation-and-prerequisites --- .../01-overview/04-install-and-prerequisites.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md index dbc39fccd..a5f7c985b 100644 --- a/docs/docs/developer/01-overview/04-install-and-prerequisites.md +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -18,7 +18,7 @@ keywords: ] --- -## Requirements: +## Requirements - [Hardware Requirements](../../guides/02-installation/06-hardware.md) @@ -29,8 +29,6 @@ keywords: ## Prerequisites -Before installing Jan, make sure you have the following installed on your computer: - - [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher) - [yarn](https://yarnpkg.com/) (version 1.22.0 or higher) - [make](https://www.gnu.org/software/make/) (version 3.81 or higher) @@ -43,10 +41,15 @@ Before installing Jan, make sure you have the following installed on your comput git clone https://github.com/janhq/jan cd jan git checkout -b DESIRED_BRANCH +``` + +2. Install dependencies + +```bash yarn install ``` -2. Run development and use Jan Desktop +3. Run development and use Jan Desktop ```bash make dev @@ -62,4 +65,4 @@ This will start the development server and open the Jan Desktop app. make build ``` -This will build the app MacOS (M1/M2/M3) for production (with code signing already done) and put the result in dist folder. +This will build the app MacOS (M1/M2/M3) for production (with code signing already done) and put the result in `dist` folder. From b14ff4cdb5fa35fa565397a5f8698c40d8bba24d Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 03:00:51 +0900 Subject: [PATCH 05/98] docs: finalize install and prerequisitites --- .../01-overview/04-install-and-prerequisites.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md index a5f7c985b..b418609f6 100644 --- a/docs/docs/developer/01-overview/04-install-and-prerequisites.md +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -35,7 +35,7 @@ keywords: ## Instructions -1. Clone the repository and install dependencies +1. **Clone the Repository:** ```bash git clone https://github.com/janhq/jan @@ -43,19 +43,19 @@ cd jan git checkout -b DESIRED_BRANCH ``` -2. Install dependencies +2. **Install Dependencie:s** ```bash yarn install ``` -3. Run development and use Jan Desktop +3. **Run Development and Use Jan Desktop** ```bash make dev ``` -This will start the development server and open the Jan Desktop app. +This command starts the development server and opens the Jan Desktop app. ## For Production Build @@ -65,4 +65,8 @@ This will start the development server and open the Jan Desktop app. make build ``` -This will build the app MacOS (M1/M2/M3) for production (with code signing already done) and put the result in `dist` folder. +This will build the app MacOS (M1/M2/M3) for production (with code signing already done) and place the result in `/electron/dist` folder. + +## Troubleshooting + +If you run into any issues due to a broken build, please check the [Stuck on a Broken Build](../../troubleshooting/stuck-on-broken-build) guide. From b946c5d3e029c88840c445930875b300989a00e7 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Tue, 23 Jan 2024 03:05:48 +0900 Subject: [PATCH 06/98] docs: correct slug --- .../developer/01-overview/04-install-and-prerequisites.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md index b418609f6..25fe2550e 100644 --- a/docs/docs/developer/01-overview/04-install-and-prerequisites.md +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -1,7 +1,7 @@ --- title: Installation and Prerequisites -slug: /developer/install-and-prerequisites -description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +slug: /developer/prereq +description: Guide to install and setup Jan for development. keywords: [ Jan AI, From 0afdee4a9856d419499c74099b7dbe986d941c8b Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 01:40:08 +0900 Subject: [PATCH 07/98] docs: update API Reference page --- docs/openapi/jan.yaml | 55 +++-- docs/openapi/specs/models.yaml | 370 ++++++++++----------------------- 2 files changed, 150 insertions(+), 275 deletions(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index bfff0ad73..9981f7308 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -67,21 +67,32 @@ paths: x-codeSamples: - lang: cURL source: | - curl http://localhost:1337/v1/chat/completions \ - -H "Content-Type: application/json" \ + curl -X 'POST' \ + 'http://127.0.0.1:1337/v1/chat/completions' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ -d '{ - "model": "tinyllama-1.1b", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello!" - } - ] - }' + "messages": [ + { + "content": "You are a helpful assistant.", + "role": "system" + }, + { + "content": "Hello!", + "role": "user" + } + ], + "model": "tinyllama-1.1b", + "stream": true, + "max_tokens": 2048, + "stop": [ + "hello" + ], + "frequency_penalty": 0, + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 + }' /models: get: operationId: listModels @@ -103,7 +114,9 @@ paths: x-codeSamples: - lang: cURL source: | - curl http://localhost:1337/v1/models + curl -X 'GET' \ + 'http://127.0.0.1:1337/v1/models' \ + -H 'accept: application/json' "/models/download/{model_id}": get: operationId: downloadModel @@ -131,7 +144,9 @@ paths: x-codeSamples: - lang: cURL source: | - curl -X POST http://localhost:1337/v1/models/download/{model_id} + curl -X 'GET' \ + 'http://127.0.0.1:1337/v1/models/download/{model_id}' \ + -H 'accept: application/json' "/models/{model_id}": get: operationId: retrieveModel @@ -162,7 +177,9 @@ paths: x-codeSamples: - lang: cURL source: | - curl http://localhost:1337/v1/models/{model_id} + curl -X 'GET' \ + 'http://127.0.0.1:1337/v1/models/{model_id}' \ + -H 'accept: application/json' delete: operationId: deleteModel tags: @@ -191,7 +208,9 @@ paths: x-codeSamples: - lang: cURL source: | - curl -X DELETE http://localhost:1337/v1/models/{model_id} + curl -X 'DELETE' \ + 'http://127.0.0.1:1337/v1/models/{model_id}' \ + -H 'accept: application/json' /threads: post: operationId: createThread diff --git a/docs/openapi/specs/models.yaml b/docs/openapi/specs/models.yaml index 418be9563..791d14880 100644 --- a/docs/openapi/specs/models.yaml +++ b/docs/openapi/specs/models.yaml @@ -18,106 +18,77 @@ components: Model: type: object properties: - type: - type: string - default: model - description: The type of the object. - version: - type: string - default: "1" - description: The version number of the model. - id: - type: string - description: Unique identifier used in chat-completions model_name, matches - folder name. - example: zephyr-7b - name: - type: string - description: Name of the model. - example: Zephyr 7B - owned_by: - type: string - description: Compatibility field for OpenAI. - default: "" - created: - type: integer - format: int64 - description: Unix timestamp representing the creation time. - description: - type: string - description: Description of the model. - state: - type: string - enum: - - null - - downloading - - ready - - starting - - stopping - description: Current state of the model. - format: - type: string - description: State format of the model, distinct from the engine. - example: ggufv3 source_url: type: string format: uri description: URL to the source of the model. - example: https://huggingface.co/TheBloke/zephyr-7B-beta-GGUF/blob/main/zephyr-7b-beta.Q4_K_M.gguf + example: https://huggingface.co/janhq/trinity-v1.2-GGUF/resolve/main/trinity-v1.2.Q4_K_M.gguf + id: + type: string + description: + Unique identifier used in chat-completions model_name, matches + folder name. + example: trinity-v1.2-7b + object: + type: string + example: model + name: + type: string + description: Name of the model. + example: Trinity-v1.2 7B Q4 + version: + type: string + default: "1.0" + description: The version number of the model. + description: + type: string + description: Description of the model. + example: Trinity is an experimental model merge using the Slerp method. Recommended for daily assistance purposes. + format: + type: string + description: State format of the model, distinct from the engine. + example: gguf settings: type: object properties: ctx_len: - type: string + type: integer description: Context length. - example: "2048" - ngl: + example: 4096 + prompt_template: type: string - description: Number of layers. - example: "100" - embedding: - type: string - description: Indicates if embedding is enabled. - example: "true" - n_parallel: - type: string - description: Number of parallel processes. - example: "4" + example: "<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant" additionalProperties: false parameters: type: object properties: temperature: - type: string - description: Temperature setting for the model. - example: "0.7" - token_limit: - type: string - description: Token limit for the model. - example: "2048" - top_k: - type: string - description: Top-k setting for the model. - example: "0" + example: 0.7 top_p: - type: string - description: Top-p setting for the model. - example: "1" + example: 0.95 stream: - type: string - description: Indicates if streaming is enabled. - example: "true" + example: true + max_tokens: + example: 4096 + stop: + example: [] + frequency_penalty: + example: 0 + presence_penalty: + example: 0 additionalProperties: false metadata: - type: object - description: Additional metadata. - assets: - type: array - items: + author: type: string - description: List of assets related to the model. - required: - - source_url + example: Jan + tags: + example: ["7B", "Merged", "Featured"] + size: + example: 4370000000, + cover: + example: "https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png" + engine: + example: nitro ModelObject: type: object properties: @@ -125,7 +96,7 @@ components: type: string description: | The identifier of the model. - example: zephyr-7b + example: ztrinity-v1.2-7b object: type: string description: | @@ -145,197 +116,82 @@ components: GetModelResponse: type: object properties: - id: - type: string - description: The identifier of the model. - example: zephyr-7b - object: - type: string - description: Type of the object, indicating it's a model. - default: model - created: - type: integer - format: int64 - description: Unix timestamp representing the creation time of the model. - owned_by: - type: string - description: The entity that owns the model. - example: _ - state: - type: string - enum: - - not_downloaded - - downloaded - - running - - stopped - description: The current state of the model. source_url: type: string format: uri description: URL to the source of the model. - example: https://huggingface.co/TheBloke/zephyr-7B-beta-GGUF/blob/main/zephyr-7b-beta.Q4_K_M.gguf - engine_parameters: - type: object - properties: - pre_prompt: - type: string - description: Predefined prompt used for setting up internal configurations. - default: "" - example: Initial setup complete. - system_prompt: - type: string - description: Prefix used for system-level prompts. - default: "SYSTEM: " - user_prompt: - type: string - description: Prefix used for user prompts. - default: "USER: " - ai_prompt: - type: string - description: Prefix used for assistant prompts. - default: "ASSISTANT: " - ngl: - type: integer - description: Number of neural network layers loaded onto the GPU for - acceleration. - minimum: 0 - maximum: 100 - default: 100 - example: 100 - ctx_len: - type: integer - description: Context length for model operations, varies based on the specific - model. - minimum: 128 - maximum: 4096 - default: 2048 - example: 2048 - n_parallel: - type: integer - description: Number of parallel operations, relevant when continuous batching is - enabled. - minimum: 1 - maximum: 10 - default: 1 - example: 4 - cont_batching: - type: boolean - description: Indicates if continuous batching is used for processing. - default: false - example: false - cpu_threads: - type: integer - description: Number of threads allocated for CPU-based inference. - minimum: 1 - example: 8 - embedding: - type: boolean - description: Indicates if embedding layers are enabled in the model. - default: true - example: true - model_parameters: + example: https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf + id: + type: string + description: + Unique identifier used in chat-completions model_name, matches + folder name. + example: mistral-ins-7b-q4 + object: + type: string + example: model + name: + type: string + description: Name of the model. + example: Mistral Instruct 7B Q4 + version: + type: string + default: "1.0" + description: The version number of the model. + description: + type: string + description: Description of the model. + example: Trinity is an experimental model merge using the Slerp method. Recommended for daily assistance purposes. + format: + type: string + description: State format of the model, distinct from the engine. + example: gguf + settings: type: object properties: ctx_len: type: integer - description: Maximum context length the model can handle. - minimum: 0 - maximum: 4096 - default: 2048 - example: 2048 - ngl: - type: integer - description: Number of layers in the neural network. - minimum: 1 - maximum: 100 - default: 100 - example: 100 - embedding: - type: boolean - description: Indicates if embedding layers are used. - default: true - example: true - n_parallel: - type: integer - description: Number of parallel processes the model can run. - minimum: 1 - maximum: 10 - default: 1 - example: 4 + description: Context length. + example: 4096 + prompt_template: + type: string + example: "[INST] {prompt} [/INST]" + additionalProperties: false + parameters: + type: object + properties: temperature: - type: number - description: Controls randomness in model's responses. Higher values lead to - more random responses. - minimum: 0 - maximum: 2 - default: 0.7 example: 0.7 - token_limit: - type: integer - description: Maximum number of tokens the model can generate in a single - response. - minimum: 1 - maximum: 4096 - default: 2048 - example: 2048 - top_k: - type: integer - description: Limits the model to consider only the top k most likely next tokens - at each step. - minimum: 0 - maximum: 100 - default: 0 - example: 0 top_p: - type: number - description: Nucleus sampling parameter. The model considers the smallest set of - tokens whose cumulative probability exceeds the top_p value. - minimum: 0 - maximum: 1 - default: 1 - example: 1 + example: 0.95 + stream: + example: true + max_tokens: + example: 4096 + stop: + example: [] + frequency_penalty: + example: 0 + presence_penalty: + example: 0 + additionalProperties: false metadata: - type: object - properties: - engine: - type: string - description: The engine used by the model. - enum: - - nitro - - openai - - hf_inference - quantization: - type: string - description: Quantization parameter of the model. - example: Q3_K_L - size: - type: string - description: Size of the model. - example: 7B - required: - - id - - object - - created - - owned_by - - state - - source_url - - parameters - - metadata + author: + type: string + example: MistralAI + tags: + example: ["7B", "Featured", "Foundation Model"] + size: + example: 4370000000, + cover: + example: "https://raw.githubusercontent.com/janhq/jan/main/models/mistral-ins-7b-q4/cover.png" + engine: + example: nitro DeleteModelResponse: type: object properties: - id: - type: string - description: The identifier of the model that was deleted. - example: model-zephyr-7B - object: - type: string - description: Type of the object, indicating it's a model. - default: model - deleted: - type: boolean - description: Indicates whether the model was successfully deleted. - example: true + message: + example: Not found StartModelResponse: type: object properties: From ce68ccf178c776dbdcda68132b95539fad93c383 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 01:47:49 +0900 Subject: [PATCH 08/98] docs: change 127.0.0.1 to localhost --- docs/openapi/jan.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index 9981f7308..90e6b9945 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -68,7 +68,7 @@ paths: - lang: cURL source: | curl -X 'POST' \ - 'http://127.0.0.1:1337/v1/chat/completions' \ + 'http://localhost:1337/v1/chat/completions' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ @@ -115,7 +115,7 @@ paths: - lang: cURL source: | curl -X 'GET' \ - 'http://127.0.0.1:1337/v1/models' \ + 'http://localhost:1337/v1/models' \ -H 'accept: application/json' "/models/download/{model_id}": get: @@ -145,7 +145,7 @@ paths: - lang: cURL source: | curl -X 'GET' \ - 'http://127.0.0.1:1337/v1/models/download/{model_id}' \ + 'http://localhost:1337/v1/models/download/{model_id}' \ -H 'accept: application/json' "/models/{model_id}": get: @@ -178,7 +178,7 @@ paths: - lang: cURL source: | curl -X 'GET' \ - 'http://127.0.0.1:1337/v1/models/{model_id}' \ + 'http://localhost:1337/v1/models/{model_id}' \ -H 'accept: application/json' delete: operationId: deleteModel @@ -209,7 +209,7 @@ paths: - lang: cURL source: | curl -X 'DELETE' \ - 'http://127.0.0.1:1337/v1/models/{model_id}' \ + 'http://localhost:1337/v1/models/{model_id}' \ -H 'accept: application/json' /threads: post: From b2d0add0060b29b61b1f3520a0a578d4c9c33471 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:33:44 +0900 Subject: [PATCH 09/98] docs: add more details --- .../01-overview/04-install-and-prerequisites.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md index 25fe2550e..6d9feeb14 100644 --- a/docs/docs/developer/01-overview/04-install-and-prerequisites.md +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -20,12 +20,19 @@ keywords: ## Requirements +### Hardware Requirements + +Ensure your system meets the following specifications to guarantee a smooth development experience: + - [Hardware Requirements](../../guides/02-installation/06-hardware.md) -- System Requirements: - - [Windows](../../install/windows/#system-requirements) - - [MacOS](../../install/mac/#system-requirements) - - [Linux](../../install/linux/#system-requirements) +### System Requirements + +Make sure your operating system meets the specific requirements for Jan development: + +- [Windows](../../install/windows/#system-requirements) +- [MacOS](../../install/mac/#system-requirements) +- [Linux](../../install/linux/#system-requirements) ## Prerequisites From 89bec2a8a9d5703c065083c540306e7da2bb3831 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:44:05 +0900 Subject: [PATCH 10/98] docs: fix typo --- docs/docs/developer/01-overview/04-install-and-prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/developer/01-overview/04-install-and-prerequisites.md b/docs/docs/developer/01-overview/04-install-and-prerequisites.md index 6d9feeb14..110f62e36 100644 --- a/docs/docs/developer/01-overview/04-install-and-prerequisites.md +++ b/docs/docs/developer/01-overview/04-install-and-prerequisites.md @@ -50,7 +50,7 @@ cd jan git checkout -b DESIRED_BRANCH ``` -2. **Install Dependencie:s** +2. **Install Dependencies** ```bash yarn install From 45c08597fefadf35d76d187e05578b72a77288ea Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:52:48 +0900 Subject: [PATCH 11/98] docs: update DeleteModelResponse --- docs/openapi/specs/models.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/openapi/specs/models.yaml b/docs/openapi/specs/models.yaml index 791d14880..776ffbd6d 100644 --- a/docs/openapi/specs/models.yaml +++ b/docs/openapi/specs/models.yaml @@ -190,8 +190,18 @@ components: DeleteModelResponse: type: object properties: - message: - example: Not found + id: + type: string + description: The identifier of the model that was deleted. + example: mistral-ins-7b-q4 + object: + type: string + description: Type of the object, indicating it's a model. + default: model + deleted: + type: boolean + description: Indicates whether the model was successfully deleted. + example: true StartModelResponse: type: object properties: From b4ffe006fed494f610c3248892c57ec711b6799f Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:55:24 +0900 Subject: [PATCH 12/98] docs: format .yaml --- docs/openapi/specs/assistants.yaml | 2 +- docs/openapi/specs/chat.yaml | 17 +++++++++++------ docs/openapi/specs/messages.yaml | 2 +- docs/openapi/specs/models.yaml | 2 +- docs/openapi/specs/threads.yaml | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/openapi/specs/assistants.yaml b/docs/openapi/specs/assistants.yaml index d784c315a..5db1f6a97 100644 --- a/docs/openapi/specs/assistants.yaml +++ b/docs/openapi/specs/assistants.yaml @@ -316,4 +316,4 @@ components: deleted: type: boolean description: Indicates whether the assistant was successfully deleted. - example: true \ No newline at end of file + example: true diff --git a/docs/openapi/specs/chat.yaml b/docs/openapi/specs/chat.yaml index b324501a8..3aeff380f 100644 --- a/docs/openapi/specs/chat.yaml +++ b/docs/openapi/specs/chat.yaml @@ -16,7 +16,8 @@ components: stream: type: boolean default: true - description: Enables continuous output generation, allowing for streaming of + description: + Enables continuous output generation, allowing for streaming of model responses. model: type: string @@ -25,23 +26,27 @@ components: max_tokens: type: number default: 2048 - description: The maximum number of tokens the model will generate in a single + description: + The maximum number of tokens the model will generate in a single response. stop: type: arrays example: - hello - description: Defines specific tokens or phrases at which the model will stop + description: + Defines specific tokens or phrases at which the model will stop generating further output/ frequency_penalty: type: number default: 0 - description: Adjusts the likelihood of the model repeating words or phrases in + description: + Adjusts the likelihood of the model repeating words or phrases in its output. presence_penalty: type: number default: 0 - description: Influences the generation of new and varied concepts in the model's + description: + Influences the generation of new and varied concepts in the model's output. temperature: type: number @@ -188,4 +193,4 @@ components: total_tokens: type: integer example: 533 - description: Total number of tokens used \ No newline at end of file + description: Total number of tokens used diff --git a/docs/openapi/specs/messages.yaml b/docs/openapi/specs/messages.yaml index d9d7d87a4..9a0799f6a 100644 --- a/docs/openapi/specs/messages.yaml +++ b/docs/openapi/specs/messages.yaml @@ -309,4 +309,4 @@ components: data: type: array items: - $ref: "#/components/schemas/MessageFileObject" \ No newline at end of file + $ref: "#/components/schemas/MessageFileObject" diff --git a/docs/openapi/specs/models.yaml b/docs/openapi/specs/models.yaml index 776ffbd6d..21276f899 100644 --- a/docs/openapi/specs/models.yaml +++ b/docs/openapi/specs/models.yaml @@ -96,7 +96,7 @@ components: type: string description: | The identifier of the model. - example: ztrinity-v1.2-7b + example: trinity-v1.2-7b object: type: string description: | diff --git a/docs/openapi/specs/threads.yaml b/docs/openapi/specs/threads.yaml index fe00f7588..825f166f1 100644 --- a/docs/openapi/specs/threads.yaml +++ b/docs/openapi/specs/threads.yaml @@ -224,4 +224,4 @@ components: deleted: type: boolean description: Indicates whether the thread was successfully deleted. - example: true \ No newline at end of file + example: true From 64cabe3d56c6759688cf71555a43b6d469d05818 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:37:08 +0900 Subject: [PATCH 13/98] chore: lint .yaml --- docs/openapi/jan.yaml | 42 ++++++++++++++--------------- docs/openapi/specs/chat.yaml | 15 ++++------- docs/openapi/specs/messages.yaml | 45 ++++++++++++++++---------------- docs/openapi/specs/models.yaml | 22 +++++++++++----- docs/openapi/specs/threads.yaml | 2 +- 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index 90e6b9945..864c80fdf 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -72,27 +72,27 @@ paths: -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ - "messages": [ - { - "content": "You are a helpful assistant.", - "role": "system" - }, - { - "content": "Hello!", - "role": "user" - } - ], - "model": "tinyllama-1.1b", - "stream": true, - "max_tokens": 2048, - "stop": [ - "hello" - ], - "frequency_penalty": 0, - "presence_penalty": 0, - "temperature": 0.7, - "top_p": 0.95 - }' + "messages": [ + { + "content": "You are a helpful assistant.", + "role": "system" + }, + { + "content": "Hello!", + "role": "user" + } + ], + "model": "tinyllama-1.1b", + "stream": true, + "max_tokens": 2048, + "stop": [ + "hello" + ], + "frequency_penalty": 0, + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 + }' /models: get: operationId: listModels diff --git a/docs/openapi/specs/chat.yaml b/docs/openapi/specs/chat.yaml index 3aeff380f..cfa391598 100644 --- a/docs/openapi/specs/chat.yaml +++ b/docs/openapi/specs/chat.yaml @@ -16,8 +16,7 @@ components: stream: type: boolean default: true - description: - Enables continuous output generation, allowing for streaming of + description: Enables continuous output generation, allowing for streaming of model responses. model: type: string @@ -26,27 +25,23 @@ components: max_tokens: type: number default: 2048 - description: - The maximum number of tokens the model will generate in a single + description: The maximum number of tokens the model will generate in a single response. stop: type: arrays example: - hello - description: - Defines specific tokens or phrases at which the model will stop + description: Defines specific tokens or phrases at which the model will stop generating further output/ frequency_penalty: type: number default: 0 - description: - Adjusts the likelihood of the model repeating words or phrases in + description: Adjusts the likelihood of the model repeating words or phrases in its output. presence_penalty: type: number default: 0 - description: - Influences the generation of new and varied concepts in the model's + description: Influences the generation of new and varied concepts in the model's output. temperature: type: number diff --git a/docs/openapi/specs/messages.yaml b/docs/openapi/specs/messages.yaml index 9a0799f6a..6f5fe1a58 100644 --- a/docs/openapi/specs/messages.yaml +++ b/docs/openapi/specs/messages.yaml @@ -1,3 +1,4 @@ +--- components: schemas: MessageObject: @@ -75,7 +76,7 @@ components: example: msg_abc123 object: type: string - description: "Type of the object, indicating it's a thread message." + description: Type of the object, indicating it's a thread message. default: thread.message created_at: type: integer @@ -88,7 +89,7 @@ components: example: thread_abc123 role: type: string - description: "Role of the sender, either 'user' or 'assistant'." + description: Role of the sender, either 'user' or 'assistant'. example: user content: type: array @@ -97,7 +98,7 @@ components: properties: type: type: string - description: "Type of content, e.g., 'text'." + description: Type of content, e.g., 'text'. example: text text: type: object @@ -110,21 +111,21 @@ components: type: array items: type: string - description: "Annotations for the text content, if any." + description: Annotations for the text content, if any. example: [] file_ids: type: array items: type: string - description: "Array of file IDs associated with the message, if any." + description: Array of file IDs associated with the message, if any. example: [] assistant_id: type: string - description: "Identifier of the assistant involved in the message, if applicable." + description: Identifier of the assistant involved in the message, if applicable. example: null run_id: type: string - description: "Run ID associated with the message, if applicable." + description: Run ID associated with the message, if applicable. example: null metadata: type: object @@ -139,7 +140,7 @@ components: example: msg_abc123 object: type: string - description: "Type of the object, indicating it's a thread message." + description: Type of the object, indicating it's a thread message. example: thread.message created_at: type: integer @@ -152,7 +153,7 @@ components: example: thread_abc123 role: type: string - description: "Role of the sender, either 'user' or 'assistant'." + description: Role of the sender, either 'user' or 'assistant'. example: user content: type: array @@ -161,7 +162,7 @@ components: properties: type: type: string - description: "Type of content, e.g., 'text'." + description: Type of content, e.g., 'text'. example: text text: type: object @@ -174,21 +175,21 @@ components: type: array items: type: string - description: "Annotations for the text content, if any." + description: Annotations for the text content, if any. example: [] file_ids: type: array items: type: string - description: "Array of file IDs associated with the message, if any." + description: Array of file IDs associated with the message, if any. example: [] assistant_id: type: string - description: "Identifier of the assistant involved in the message, if applicable." + description: Identifier of the assistant involved in the message, if applicable. example: null run_id: type: string - description: "Run ID associated with the message, if applicable." + description: Run ID associated with the message, if applicable. example: null metadata: type: object @@ -199,7 +200,7 @@ components: properties: object: type: string - description: "Type of the object, indicating it's a list." + description: Type of the object, indicating it's a list. default: list data: type: array @@ -226,7 +227,7 @@ components: example: msg_abc123 object: type: string - description: "Type of the object, indicating it's a thread message." + description: Type of the object, indicating it's a thread message. example: thread.message created_at: type: integer @@ -239,7 +240,7 @@ components: example: thread_abc123 role: type: string - description: "Role of the sender, either 'user' or 'assistant'." + description: Role of the sender, either 'user' or 'assistant'. example: user content: type: array @@ -248,7 +249,7 @@ components: properties: type: type: string - description: "Type of content, e.g., 'text'." + description: Type of content, e.g., 'text'. text: type: object properties: @@ -260,20 +261,20 @@ components: type: array items: type: string - description: "Annotations for the text content, if any." + description: Annotations for the text content, if any. file_ids: type: array items: type: string - description: "Array of file IDs associated with the message, if any." + description: Array of file IDs associated with the message, if any. example: [] assistant_id: type: string - description: "Identifier of the assistant involved in the message, if applicable." + description: Identifier of the assistant involved in the message, if applicable. example: null run_id: type: string - description: "Run ID associated with the message, if applicable." + description: Run ID associated with the message, if applicable. example: null metadata: type: object diff --git a/docs/openapi/specs/models.yaml b/docs/openapi/specs/models.yaml index 21276f899..40e6abaaf 100644 --- a/docs/openapi/specs/models.yaml +++ b/docs/openapi/specs/models.yaml @@ -43,7 +43,9 @@ components: description: type: string description: Description of the model. - example: Trinity is an experimental model merge using the Slerp method. Recommended for daily assistance purposes. + example: + Trinity is an experimental model merge using the Slerp method. + Recommended for daily assistance purposes. format: type: string description: State format of the model, distinct from the engine. @@ -82,11 +84,14 @@ components: type: string example: Jan tags: - example: ["7B", "Merged", "Featured"] + example: + - 7B + - Merged + - Featured size: example: 4370000000, cover: - example: "https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png" + example: https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png engine: example: nitro ModelObject: @@ -141,7 +146,9 @@ components: description: type: string description: Description of the model. - example: Trinity is an experimental model merge using the Slerp method. Recommended for daily assistance purposes. + example: + Trinity is an experimental model merge using the Slerp method. + Recommended for daily assistance purposes. format: type: string description: State format of the model, distinct from the engine. @@ -180,11 +187,14 @@ components: type: string example: MistralAI tags: - example: ["7B", "Featured", "Foundation Model"] + example: + - 7B + - Featured + - Foundation Model size: example: 4370000000, cover: - example: "https://raw.githubusercontent.com/janhq/jan/main/models/mistral-ins-7b-q4/cover.png" + example: https://raw.githubusercontent.com/janhq/jan/main/models/mistral-ins-7b-q4/cover.png engine: example: nitro DeleteModelResponse: diff --git a/docs/openapi/specs/threads.yaml b/docs/openapi/specs/threads.yaml index 825f166f1..40b2463fa 100644 --- a/docs/openapi/specs/threads.yaml +++ b/docs/openapi/specs/threads.yaml @@ -142,7 +142,7 @@ components: example: Jan instructions: type: string - description: | + description: > The instruction of assistant, defaults to "Be my grammar corrector" model: type: object From 49670a84c3d0e96fccb7abe2016f1665972d1231 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:49:44 +0700 Subject: [PATCH 14/98] Update 06-unexpected-token.mdx --- docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx b/docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx index 973001f1b..1de609ffa 100644 --- a/docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx +++ b/docs/docs/guides/08-troubleshooting/06-unexpected-token.mdx @@ -17,4 +17,8 @@ keywords: ] --- -1. You may receive an error response `Error occurred: Unexpected token '<', " Date: Fri, 26 Jan 2024 01:09:24 +0900 Subject: [PATCH 15/98] docs: add additional reasons to somethings amiss --- docs/docs/guides/08-troubleshooting/02-somethings-amiss.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/guides/08-troubleshooting/02-somethings-amiss.mdx b/docs/docs/guides/08-troubleshooting/02-somethings-amiss.mdx index a5669e36d..4e16e362a 100644 --- a/docs/docs/guides/08-troubleshooting/02-somethings-amiss.mdx +++ b/docs/docs/guides/08-troubleshooting/02-somethings-amiss.mdx @@ -45,7 +45,9 @@ This may occur due to several reasons. Please follow these steps to resolve it: 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: +6. If you're using Linux, please ensure that your system meets the following requirements gcc 11, g++ 11, cpp 11, or higher, refer to this [link](https://jan.ai/guides/troubleshooting/gpu-not-used/#specific-requirements-for-linux) for more information. + +7. 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: From d26e97f4a7b13dde738074d41757c1f0dd0be431 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 26 Jan 2024 01:33:45 +0900 Subject: [PATCH 16/98] docs: add OpenAI model list --- .../04-using-models/03-integrate-with-remote-server.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx b/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx index 533797fca..f0db1bd55 100644 --- a/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx +++ b/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx @@ -65,6 +65,13 @@ Navigate to the `~/jan/models` folder. Create a folder named `gpt-3.5-turbo-16k` } ``` +:::tip + +- You can find the list of available models in the [OpenAI Platform](https://platform.openai.com/docs/models/overview). +- Please note that the `id` property need to match the model name in the list. For example, if you want to use the [GPT-4 Turbo](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo), you need to set the `id` property as `gpt-4-1106-preview`. + +::: + ### 2. Configure OpenAI API Keys You can find your API keys in the [OpenAI Platform](https://platform.openai.com/api-keys) and set the OpenAI API keys in `~/jan/engines/openai.json` file. From fd872c3de07a1d070d7e5abd07af144d4dc6cbed Mon Sep 17 00:00:00 2001 From: Service Account Date: Thu, 1 Feb 2024 18:56:52 +0000 Subject: [PATCH 17/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34eecc9f3..ad672969c 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From ae23127ddb5a25ce5e55772481fde44e72585d3e Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 2 Feb 2024 07:53:01 +0900 Subject: [PATCH 18/98] docs: add trouble 07-undefined-issue --- .../08-troubleshooting/07-undefined-issue.mdx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx diff --git a/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx b/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx new file mode 100644 index 000000000..b45bfd86e --- /dev/null +++ b/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx @@ -0,0 +1,26 @@ +--- +title: Undefined Issue +slug: /troubleshooting/undefined-issue +description: Undefined issue troubleshooting guide. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + troubleshooting, + undefined issue, + ] +--- + +You may encounter an "undefined" issue when using Jan. Here are some troubleshooting steps to help you resolve the issue. + +1. Try wiping the Jan folder and reopening the Jan app and see if the issue persists. +2. If the issue persists, try to go `~/jan/extensions/@janhq/inference-nitro-extensions/dist/bin//nitro` and run the nitro manually and see if you get any error messages. +3. Resolve the error messages you get from the nitro and see if the issue persists. +4. Reopen the Jan app and see if the issue is resolved. +5. If the issue persists, please contact us at [Jan Discord](https://discord.gg/mY69SZaMaC). From 8ff04f4db96340546809af7e027a4105abc043c0 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:03:39 +0900 Subject: [PATCH 19/98] docs: add app log --- docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx b/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx index b45bfd86e..4aba6438d 100644 --- a/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx +++ b/docs/docs/guides/08-troubleshooting/07-undefined-issue.mdx @@ -23,4 +23,4 @@ You may encounter an "undefined" issue when using Jan. Here are some troubleshoo 2. If the issue persists, try to go `~/jan/extensions/@janhq/inference-nitro-extensions/dist/bin//nitro` and run the nitro manually and see if you get any error messages. 3. Resolve the error messages you get from the nitro and see if the issue persists. 4. Reopen the Jan app and see if the issue is resolved. -5. If the issue persists, please contact us at [Jan Discord](https://discord.gg/mY69SZaMaC). +5. If the issue persists, please share with us the [app logs](https://jan.ai/troubleshooting/how-to-get-error-logs/) via [Jan Discord](https://discord.gg/mY69SZaMaC). From 9e1c41b8978769903b8342d084e926901d38d2c9 Mon Sep 17 00:00:00 2001 From: NamH Date: Fri, 2 Feb 2024 10:45:10 +0700 Subject: [PATCH 20/98] fix: chat using web interface (#1889) Signed-off-by: James Co-authored-by: James --- .../rollup.config.ts | 3 ++ .../src/@types/global.d.ts | 1 + .../src/helpers/sse.ts | 3 +- .../inference-nitro-extension/src/index.ts | 44 ++++++++++++++----- web/containers/Toast/index.tsx | 16 +++---- 5 files changed, 47 insertions(+), 20 deletions(-) diff --git a/extensions/inference-nitro-extension/rollup.config.ts b/extensions/inference-nitro-extension/rollup.config.ts index 374a054cd..77a9fb208 100644 --- a/extensions/inference-nitro-extension/rollup.config.ts +++ b/extensions/inference-nitro-extension/rollup.config.ts @@ -27,6 +27,9 @@ export default [ TROUBLESHOOTING_URL: JSON.stringify( "https://jan.ai/guides/troubleshooting" ), + JAN_SERVER_INFERENCE_URL: JSON.stringify( + "http://localhost:1337/v1/chat/completions" + ), }), // Allow json resolution json(), diff --git a/extensions/inference-nitro-extension/src/@types/global.d.ts b/extensions/inference-nitro-extension/src/@types/global.d.ts index bc126337f..7a4fb4805 100644 --- a/extensions/inference-nitro-extension/src/@types/global.d.ts +++ b/extensions/inference-nitro-extension/src/@types/global.d.ts @@ -1,6 +1,7 @@ declare const NODE: string; declare const INFERENCE_URL: string; declare const TROUBLESHOOTING_URL: string; +declare const JAN_SERVER_INFERENCE_URL: string; /** * The response from the initModel function. diff --git a/extensions/inference-nitro-extension/src/helpers/sse.ts b/extensions/inference-nitro-extension/src/helpers/sse.ts index c6352383d..aab260828 100644 --- a/extensions/inference-nitro-extension/src/helpers/sse.ts +++ b/extensions/inference-nitro-extension/src/helpers/sse.ts @@ -6,6 +6,7 @@ import { Observable } from "rxjs"; * @returns An Observable that emits the generated response as a string. */ export function requestInference( + inferenceUrl: string, recentMessages: any[], model: Model, controller?: AbortController @@ -17,7 +18,7 @@ export function requestInference( stream: true, ...model.parameters, }); - fetch(INFERENCE_URL, { + fetch(inferenceUrl, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/extensions/inference-nitro-extension/src/index.ts b/extensions/inference-nitro-extension/src/index.ts index 9f1f00263..81a0031ac 100644 --- a/extensions/inference-nitro-extension/src/index.ts +++ b/extensions/inference-nitro-extension/src/index.ts @@ -68,35 +68,48 @@ export default class JanInferenceNitroExtension extends InferenceExtension { */ private nitroProcessInfo: any = undefined; + private inferenceUrl = ""; + /** * Subscribes to events emitted by the @janhq/core package. */ async onLoad() { if (!(await fs.existsSync(JanInferenceNitroExtension._homeDir))) { - await fs - .mkdirSync(JanInferenceNitroExtension._homeDir) - .catch((err: Error) => console.debug(err)); + try { + await fs.mkdirSync(JanInferenceNitroExtension._homeDir); + } catch (e) { + console.debug(e); + } } + // init inference url + // @ts-ignore + const electronApi = window?.electronAPI; + this.inferenceUrl = INFERENCE_URL; + if (!electronApi) { + this.inferenceUrl = JAN_SERVER_INFERENCE_URL; + } + console.debug("Inference url: ", this.inferenceUrl); + if (!(await fs.existsSync(JanInferenceNitroExtension._settingsDir))) await fs.mkdirSync(JanInferenceNitroExtension._settingsDir); this.writeDefaultEngineSettings(); // Events subscription events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => - this.onMessageRequest(data), + this.onMessageRequest(data) ); events.on(ModelEvent.OnModelInit, (model: Model) => - this.onModelInit(model), + this.onModelInit(model) ); events.on(ModelEvent.OnModelStop, (model: Model) => - this.onModelStop(model), + this.onModelStop(model) ); events.on(InferenceEvent.OnInferenceStopped, () => - this.onInferenceStopped(), + this.onInferenceStopped() ); // Attempt to fetch nvidia info @@ -121,7 +134,7 @@ export default class JanInferenceNitroExtension extends InferenceExtension { } else { await fs.writeFileSync( engineFile, - JSON.stringify(this._engineSettings, null, 2), + JSON.stringify(this._engineSettings, null, 2) ); } } catch (err) { @@ -149,7 +162,7 @@ export default class JanInferenceNitroExtension extends InferenceExtension { this.getNitroProcesHealthIntervalId = setInterval( () => this.periodicallyGetNitroHealth(), - JanInferenceNitroExtension._intervalHealthCheck, + JanInferenceNitroExtension._intervalHealthCheck ); } @@ -206,7 +219,11 @@ export default class JanInferenceNitroExtension extends InferenceExtension { return new Promise(async (resolve, reject) => { if (!this._currentModel) return Promise.reject("No model loaded"); - requestInference(data.messages ?? [], this._currentModel).subscribe({ + requestInference( + this.inferenceUrl, + data.messages ?? [], + this._currentModel + ).subscribe({ next: (_content: any) => {}, complete: async () => { resolve(message); @@ -254,7 +271,12 @@ export default class JanInferenceNitroExtension extends InferenceExtension { ...(this._currentModel || {}), ...(data.model || {}), }; - requestInference(data.messages ?? [], model, this.controller).subscribe({ + requestInference( + this.inferenceUrl, + data.messages ?? [], + model, + this.controller + ).subscribe({ next: (content: any) => { const messageContent: ThreadContent = { type: ContentType.Text, diff --git a/web/containers/Toast/index.tsx b/web/containers/Toast/index.tsx index 7cffa89b9..eae340fee 100644 --- a/web/containers/Toast/index.tsx +++ b/web/containers/Toast/index.tsx @@ -19,8 +19,8 @@ const ErrorIcon = () => { xmlns="http://www.w3.org/2000/svg" > @@ -38,8 +38,8 @@ const WarningIcon = () => { xmlns="http://www.w3.org/2000/svg" > @@ -57,8 +57,8 @@ const SuccessIcon = () => { xmlns="http://www.w3.org/2000/svg" > @@ -76,8 +76,8 @@ const DefaultIcon = () => { xmlns="http://www.w3.org/2000/svg" > From ce63e1805e403d83f746e60912455e1f5c9b33e2 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:03:18 +0900 Subject: [PATCH 21/98] docs: update what is tracked --- docs/docs/about/01-README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/about/01-README.md b/docs/docs/about/01-README.md index 3b2759513..c69a35751 100644 --- a/docs/docs/about/01-README.md +++ b/docs/docs/about/01-README.md @@ -111,8 +111,9 @@ Adhering to Jan's privacy preserving philosophy, our analytics philosophy is to #### What is tracked 1. By default, Github tracks downloads and device metadata for all public Github repos. This helps us troubleshoot & ensure cross platform support. -1. We use Posthog to track a single `app.opened` event without additional user metadata, in order to understand retention. -1. Additionally, we plan to enable a `Settings` feature for users to turn off all tracking. +2. We use [Umami](https://umami.is/) to collect, analyze, and understand application data while maintaining visitor privacy and data ownership. We are using the Umami Cloud in Europe to ensure GDPR compliant. Please see [Umami Privacy Policy](https://umami.is/privacy) for more details. +3. We use Umami to track a single `app.opened` event without additional user metadata, in order to understand retention. In addition, we track `app.event` to understand app version usage. +4. Additionally, we plan to enable a `Settings` feature for users to turn off all tracking. #### Request for help From f7c591eca994d0719e0c94bfc0fffdfc7d494722 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:08:39 +0900 Subject: [PATCH 22/98] docs: fix typo --- docs/docs/about/01-README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/about/01-README.md b/docs/docs/about/01-README.md index c69a35751..d5d3b8dc2 100644 --- a/docs/docs/about/01-README.md +++ b/docs/docs/about/01-README.md @@ -110,8 +110,8 @@ Adhering to Jan's privacy preserving philosophy, our analytics philosophy is to #### What is tracked -1. By default, Github tracks downloads and device metadata for all public Github repos. This helps us troubleshoot & ensure cross platform support. -2. We use [Umami](https://umami.is/) to collect, analyze, and understand application data while maintaining visitor privacy and data ownership. We are using the Umami Cloud in Europe to ensure GDPR compliant. Please see [Umami Privacy Policy](https://umami.is/privacy) for more details. +1. By default, Github tracks downloads and device metadata for all public GitHub repositories. This helps us troubleshoot & ensure cross-platform support. +2. We use [Umami](https://umami.is/) to collect, analyze, and understand application data while maintaining visitor privacy and data ownership. We are using the Umami Cloud in Europe to ensure GDPR compliance. Please see [Umami Privacy Policy](https://umami.is/privacy) for more details. 3. We use Umami to track a single `app.opened` event without additional user metadata, in order to understand retention. In addition, we track `app.event` to understand app version usage. 4. Additionally, we plan to enable a `Settings` feature for users to turn off all tracking. From 071c9286c42f5976214cd739aecd463fbc56b78c Mon Sep 17 00:00:00 2001 From: Service Account Date: Fri, 2 Feb 2024 09:12:13 +0000 Subject: [PATCH 23/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ad672969c..ec35170f6 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 81bbd838a77c7934ff63077544d475e32ea14868 Mon Sep 17 00:00:00 2001 From: Service Account Date: Sat, 3 Feb 2024 08:41:33 +0000 Subject: [PATCH 24/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ec35170f6..783c37393 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 135a7773082f2719b98896aa7d200fde86126c34 Mon Sep 17 00:00:00 2001 From: Service Account Date: Sat, 3 Feb 2024 10:05:06 +0000 Subject: [PATCH 25/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 783c37393..6a7b280ec 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 4472bb1b9b6d264fbc89a7c9206637e88047e6dd Mon Sep 17 00:00:00 2001 From: Service Account Date: Sun, 4 Feb 2024 04:29:58 +0000 Subject: [PATCH 26/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6a7b280ec..8939ed598 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 0b44649553070770ed4ccb43c0cf352ce3e91152 Mon Sep 17 00:00:00 2001 From: Service Account Date: Sun, 4 Feb 2024 06:41:07 +0000 Subject: [PATCH 27/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8939ed598..16574ad41 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 3fecdc3c332af1fe9da40a9828a85c24664125e4 Mon Sep 17 00:00:00 2001 From: Service Account Date: Sun, 4 Feb 2024 09:02:09 +0000 Subject: [PATCH 28/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 16574ad41..02a5aa6e9 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 480a1d9cc1e7b86e669f64eb9691902a9e394cc4 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Sun, 4 Feb 2024 21:02:40 +0700 Subject: [PATCH 29/98] Cloudflare R2 clean every 10 days (#1917) Co-authored-by: Hien To --- .../workflows/clean-cloudflare-page-preview-url-and-r2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clean-cloudflare-page-preview-url-and-r2.yml b/.github/workflows/clean-cloudflare-page-preview-url-and-r2.yml index 620f74714..de761ca69 100644 --- a/.github/workflows/clean-cloudflare-page-preview-url-and-r2.yml +++ b/.github/workflows/clean-cloudflare-page-preview-url-and-r2.yml @@ -55,10 +55,10 @@ jobs: steps: - name: install-aws-cli-action uses: unfor19/install-aws-cli-action@v1 - - name: Delete object older than 7 days + - name: Delete object older than 10 days run: | # Get the list of objects in the 'latest' folder - OBJECTS=$(aws s3api list-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --query 'Contents[?LastModified<`'$(date -d "$current_date -30 days" -u +"%Y-%m-%dT%H:%M:%SZ")'`].{Key: Key}' --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | jq -c .) + OBJECTS=$(aws s3api list-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --query 'Contents[?LastModified<`'$(date -d "$current_date -10 days" -u +"%Y-%m-%dT%H:%M:%SZ")'`].{Key: Key}' --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | jq -c .) # Create a JSON file for the delete operation echo "{\"Objects\": $OBJECTS, \"Quiet\": false}" > delete.json From ccbe18e5b8417fb44dc8e130782c7da60c348ee8 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 5 Feb 2024 03:29:06 +0000 Subject: [PATCH 30/98] Update README.md with Stable Download URLs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 02a5aa6e9..e1a06820e 100644 --- a/README.md +++ b/README.md @@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Stable (Recommended) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 20e7d3071a49f34ed6c290dda775b64dcdd57afc Mon Sep 17 00:00:00 2001 From: SamPatt Date: Sun, 4 Feb 2024 23:20:21 -0500 Subject: [PATCH 31/98] Updates Guide Using the Local Server --- docs/docs/guides/05-using-server/01-server.md | 33 ------ .../guides/05-using-server/01-start-server.md | 70 ++++++++++++ .../guides/05-using-server/02-using-server.md | 103 ++++++++++++++++++ .../05-using-server/assets/api-reference.png | Bin 0 -> 16082 bytes .../05-using-server/assets/chat-example.png | Bin 0 -> 81919 bytes .../05-using-server/assets/choose-model.png | Bin 0 -> 94823 bytes .../05-using-server/assets/local-api-view.png | Bin 0 -> 26208 bytes .../05-using-server/assets/running-server.png | Bin 0 -> 97561 bytes .../assets/server-settings.png | Bin 0 -> 35685 bytes 9 files changed, 173 insertions(+), 33 deletions(-) delete mode 100644 docs/docs/guides/05-using-server/01-server.md create mode 100644 docs/docs/guides/05-using-server/01-start-server.md create mode 100644 docs/docs/guides/05-using-server/02-using-server.md create mode 100644 docs/docs/guides/05-using-server/assets/api-reference.png create mode 100644 docs/docs/guides/05-using-server/assets/chat-example.png create mode 100644 docs/docs/guides/05-using-server/assets/choose-model.png create mode 100644 docs/docs/guides/05-using-server/assets/local-api-view.png create mode 100644 docs/docs/guides/05-using-server/assets/running-server.png create mode 100644 docs/docs/guides/05-using-server/assets/server-settings.png diff --git a/docs/docs/guides/05-using-server/01-server.md b/docs/docs/guides/05-using-server/01-server.md deleted file mode 100644 index 952b7399f..000000000 --- a/docs/docs/guides/05-using-server/01-server.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Connect to Server -description: Connect to Jan's built-in API server. -keywords: - [ - Jan AI, - Jan, - ChatGPT alternative, - local AI, - private AI, - conversational AI, - no-subscription fee, - large language model, - ] ---- - -:::warning - -This page is under construction. - -::: - -Jan ships with a built-in API server, that can be used as a drop-in, local replacement for OpenAI's API. - -Jan runs on port `1337` by default, but this can (soon) be changed in Settings. - -1. Go to Settings > Advanced > Enable API Server - -2. Go to http://localhost:1337 for the API docs. - -3. In terminal, simply CURL... - -Note: Some UI states may be broken when in Server Mode. diff --git a/docs/docs/guides/05-using-server/01-start-server.md b/docs/docs/guides/05-using-server/01-start-server.md new file mode 100644 index 000000000..4b94b53a2 --- /dev/null +++ b/docs/docs/guides/05-using-server/01-start-server.md @@ -0,0 +1,70 @@ +--- +title: Start Local Server +description: How to run Jan's built-in API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + ] +--- + +Jan ships with a built-in API server that can be used as a drop-in, local replacement for OpenAI's API. You can run your server by following these simple steps. + +## Open Local API Server View + +Navigate by clicking the `Local API Server` icon on the left side of your screen, as shown in the image below. + +

+ +![local-api-view](./assets/local-api-view.png) + +## Choose your model + +On the top right of your screen under `Model Settings`, set the LLM that your local server will be running. You can choose from any of the models already installed, or pick a new model by clicking `Explore the Hub`. + +

+ +![choose-model](./assets/choose-model.png) + +## Set your Server Options + +On the left side of your screen you can set custom server options. + +

+ +![server-settings](./assets/server-settings.png) + +### Local Server Address + +By default, Jan will be accessible only on localhost `127.0.0.1`. This means a local server can only be accessed on the same machine where the server is being run. + +You can make the local server more accessible by clicking on the address and choosing `0.0.0.0` instead, which allows the server to be accessed from other devices on the local network. This is less secure than choosing localhost, and should be done with caution. + +### Port + +Jan runs on port `1337` by default, but this can be changed. + +### CORS + +Cross-Origin Resource Sharing (CORS) manages resource access on the local server from external domains. Enabled for security by default, it can be disabled if needed. + +### Verbose Server Logs + +The center of the screen displays the server logs as the local server runs. This option provides extensive details about server activities. + +## Start Server + +Click the `Start Server` button on the top left of your screen. You will see the server log display a message such as `Server listening at http://127.0.0.1:1337`, and the `Start Server` button will change to a red `Stop Server` button. + +

+ +![running-server](./assets/running-server.png) + +Your server is now running. Next, learn how to use your local API server. + diff --git a/docs/docs/guides/05-using-server/02-using-server.md b/docs/docs/guides/05-using-server/02-using-server.md new file mode 100644 index 000000000..2ac0480fa --- /dev/null +++ b/docs/docs/guides/05-using-server/02-using-server.md @@ -0,0 +1,103 @@ +--- +title: Using Local Server +description: How to use Jan's built-in API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + ] +--- + +Jan's built-in API server is compatible with [OpenAI's API](https://platform.openai.com/docs/api-reference) and can be used as a drop-in, local replacement. Follow these steps to use the API server. + +## Open the API Reference + +Jan contains a comprehensive API reference. This reference displays all the API endpoints available, gives you examples requests and responses, and allows you to execute them in browser. + +On the top left of your screen below the red `Stop Server` button is the blue `API Reference`. Clicking this will open the reference in browser. + +

+ +![api-reference](./assets/api-reference.png) + +Scroll through the various available endpoints to learn what options are available. + +### Chat + +In the Chat section of the API reference, you will see an example JSON request body. + +

+ +![chat-example](./assets/chat-example.png) + +With your local server running, you can click the `Try it out` button on the top left, then the blue `Execute` button below the JSON. The browser will send the example request to your server, and display the response body below. + +Use the API endpoints, request and response body examples as models for your own application. + +### Curl request example + +Here's an example curl request with a local server running `tinyllama-1.1b`: + +

+ +```json +curl -X 'POST' \ + 'http://localhost:1337/v1/chat/completions' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "messages": [ + { + "content": "You are a helpful assistant.", + "role": "system" + }, + { + "content": "Hello!", + "role": "user" + } + ], + "model": "tinyllama-1.1b", + "stream": true, + "max_tokens": 2048, + "stop": [ + "hello" + ], + "frequency_penalty": 0, + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 +}' +``` + +### Response body example + +```json +{ + "choices": [ + { + "finish_reason": null, + "index": 0, + "message": { + "content": "Hello user. What can I help you with?", + "role": "assistant" + } + } + ], + "created": 1700193928, + "id": "ebwd2niJvJB1Q2Whyvkz", + "model": "_", + "object": "chat.completion", + "system_fingerprint": "_", + "usage": { + "completion_tokens": 500, + "prompt_tokens": 33, + "total_tokens": 533 + } +} +``` \ No newline at end of file diff --git a/docs/docs/guides/05-using-server/assets/api-reference.png b/docs/docs/guides/05-using-server/assets/api-reference.png new file mode 100644 index 0000000000000000000000000000000000000000..8cfef431c348917bf4d46ae0b9de25ee8d4b09c6 GIT binary patch literal 16082 zcmb`u1yGeyv^Go#C?Nt0A}JEmB_U}bAgOehbV_%Ll#~e42nvXFcb9ZacT1OaoNt|b z?|gIrng9FepKmTR4CfqPHt*hht@S+XdG;IpMot0``vEoz3JTt9NihW!6jXNjzIziB zK5=_jSPnmK*uQwKbQ8WjZW;!`ds2s2Y7UAv#tzQB);3l~toHBhjEt=9O>G=@ z(HlhIB39%@FYS!<9n5U3sg=yEj8GIEt*JSlP>UNGP;+r`J)!305O~5Pz|BqlMwa@O zqEhI$OG*?JYLwSv&y`$~wr8B9?zhIS*(zC|;I5E><3{VJd(0a(0#YNS!dAJDNJ&ZW z2Q(!;wcn^=lHk#6M8R!Im5yawS>2u}3HrRUrCfVBh3n};AR`+arrzdg z-u(by{rh)`iB&4YRCQeax%-8pf`V@TNK0FvYY?6DJkd_xi4_nwbxHm?ny1-5rt6K@ zc(Br~UTXSwz$0_G^LTq||I9<|)vJYl7qvP!9(>_{cU}hIX8qyS@jO!g9YNLK^_7{C zl{Euq;(EARar8O1PMsQoqnc*pg%4G7zAi5t(#ym>d;R+L*K$NN4m&kXlK1tQ@0di1`-!!b zw6yDX*%(S3pAF^aSb^vc@2oe%6UDl60W(RC0RscWW512$`9g)mxs|4 zte_xVGF}-4Ma2xW;hzS)i5Qzb`|NAI?rD1L>4nx2~aOib)kNC^7yYHyMi)z>!L_kBsWzc#P)vzm?(bp&(_h)V{ zOT3V)o!(>s9$7e<4zp^0^y%qoBiCAzz1+YBr`tbM)aCDil!OG4JQkxjse2Q6a^CC0 zKdIDWo2YioK{2S9$|x*+12bpvaris%U3?{b;KzcpMEE_O-3El$KodR9!Jq`yEo!T& z@_Pi4`Pwzu)c^5|YLOIqdby;>w*w#D!S*S&nbQ$tBGEBP@pO7KBXY6Djhe2UqvC6H z8~^7cZ%lkjE$jGC&C+7ajlSpy9giX;ON@Kbx<$_arqR$0AqVZ|OYB{As)?v#H%WGk zLE*Arm8n_^7yO`+!o#TQf4iP>FEy|(ed9Su1*gBrbQeKyu{y+@V@ke;qY@tup5nHwZ#*8tv-wj+BHNx zu^i{{I$viHIa`iu9O=%<@4-4tgP=n zW|h6)WH#>?{7Dwy^LO<28g<98qWs$&i{O0syU@7z!A{L?{j&+)M7+JZjd=%wqN(lY zQ(kMy&L}})VRZ1t;fc7uT3%k}@I3!#x8OM6h)VP5QBG|NlqI~af;NNi`Yi!I2&tdL z>*?>>!*kV&J~TJK*r~s;HZU;g=qi;bkt{p2Uy2MQ09 zs{dy<>$BwIE;%w;`lz_x)yDp#FBlrQP+15=+Gu>n zEqPzd$V7{WJ;0E2?J+KK*c_F0`}g;uPpQS2oS2x??kuZH4$t4a|K8_mRuDhaab5YE zx(;c1@7b5AsJ5kjrH$x4mu^TM(ZA7oTZpw3@A{yiT+IqOOWs5jgCaFrPR_S7^ih|G zDTup~ODSl&US})5Tyt)ld5!-PVsc*iU6W?N6G_1@7$f_Kz6Zs`7Pc@dDJisj<%dcC zHxGFC#AcR!ucEddaqaGQwBbKt`Fq`ADE&3j`SEslNxz7+o0~v)EW63s!3tcN+kMpi?y|)x=sFy#(w)hioySlKP3Ttf~9wE-1x|7*wR}q(_eAZfDyUa(jzoHxf0$|FfaGQir+xH89Nlw-d;Ze;j zfl;LN^&cxhy2kdO>&wiF#PACBWr9+l-ne-(! z*2e03sUvd)Fc*q19XGe47F#SJ6b|*AH=-^msB*?SI5hO`v(bEQ%{;q^Qit`SXK!X4 zhGmh%PZS%4OnaWp_^zjTodf(Ka|I%q+04w$piF~~k@2;4^*ViLcQ;P7DzT1{D3l5U zf=EC+9BOWpK3tX>wZFI9Yu*hYWOmk6roqqq1On&s}C2q@|sLs3mce-zH?d`pPEhY5@N^)>e z$4aa&n~%&lA$2h^sM;((hK7cJeoeQ`yl=rn>rgjIR<_n@@VdJ3nK{4m#eZP?=Ce&M zR#w)5E}_lb60{U`br&A%X*G>9bGkMm9Q?aeJM7@vWBapJy2sagmi6KkdV zW3+3bprD{P5gHo$tkH36{4I*vK&meiy&k}(Cr7;T|3yTmqf-&of7Nc`9OL3#N4GPR z64bB#Sf@ZT>YdX3p@&A90u9pil}y-uJ0(?d{#I!9*(nIKF2Pe)pac zv#3$i(6HB^{y?#taU2VlJVURktW*;dgQ%wW`L{EpDJDkgGLMS748KW|b~2s+)36m8CddPzfr465LW2$E+amwPV>o}Qa6MLJd;Pg-Dne)uvV zsC_At%KMV@PJJSu&28+0q$KjrzP_08aWz!5JdMDXhdxk*WW>a@*i?-~*c?Cc3)OCv zx6CH{+aOqkYDAk=i^oJ+*T_PX75A)NP)>IGA2Tto>RD6LM>YMVsF|qRMXA4YzxASQ zOlK*lpnehRERughbR>jR2o~GSiQJ|X>WE?0MEN^a!5*Oh9_k``A;2;L?z||fTDGskx`dO+-MsNyejRx6S&W9lJWvXOk}>6JmR~|K;7IHR zI0*wrymWYWdbXC*%}PZ=A^|vzGG6D-hvJ8YkC_VmR|_D`ampSSFQ%G#66;kC zhm%l1#jhJSew#%+6oL8{LHhUASm-K`Ya{q4zbM}I3;I0B;V&zAe1~$Cnv=JBCtF3j zPZZ&S{4TeikxCKq9Iy8jgoR%L#KvXe)^EN6(S%+wJUkqsZft4Ef{ux+2VAxGbTI@8 zv;csEHO@BJ*x1?HI|~qqZ9iUxAZc@i@UhxJEz|)_A6OqsN6)|(RG(gB^hU!21YY#g z;japWq%SKJ>bec2|OVy-Ldw+NlnO0$Q?JbT4G{R>9GPj z8_iGVXLu-VcI<+Lh-fDDi{m(+jv4fyCH#xNKTU~Wh4Ct#Pg4Kl+tsN@eRYloUhqwu zx+CI?<85?mdinrBDU^eQgP`Ex@2{iieHvB{_ge6|T@MWN8?N214`Lh0D=r7qB+zes zF#7ZBZJf-{2eF!~2^RUN^X*jL%jHwn?|`>h&ebQwUvt{b3ZdlJpKqi(PFwTQyzuf8 z(e=LKd6uetmx>C%5m^hmRnpy0c2I!L;uVZ_7aMh})i|-zM+F1vQ_0?$n3$M$p7Vrl z_t&X&d#j|>s;cX$yzZ%*`B^~tx#JG;#QTzt;?c#+@}j$}q)Z~t-z7&aBaQOf4n6G| zb^gdtW5vFH`#Q_xAy{6*z zg@pw|5)uZV%ge)l8ioC@m0WDG(nCW-Xhzlm_Z$WEE0jB{i8dk|d4w$icsMMoqib#7 z>!B*&M5SeAm6ei`5))hA^+vd-=7&At`BH8*rJ@!aM#lGP#+u|AR8Dk?W8h2o2pGrK z=xByA<*87Y0aS=GhZ%VjWfP!<(5`kM$Qfbs@i<$FRYu%Vmdu_KDY(q2RGIl`f~e4w z_B{=0B=qAEBJVsS9y`XTUM0V2<3C~Ewl(Wx(x3S;Udr@KO!Lmw1Z`W}8zlveP30Wc z{r!C{#}VZx=6`NRb&9=sfd(X;7x?w!R!KkRWnLz}$f*x9i=1kZjU(WYuVU^yfU9Z1 z6rYBOOrRvbt~`t<@7Zhj(4bjKlZc=Mj!Z;MEN*N}4;74t+qma(g@=Q~6HZP}fG0q& zriwW)GJen6wQuhCh25UOvKpG05K&NQFRbf9!wK+r3n005#V#^Bx)t!MU7Ed4DNA5& zb=7WE-6S$O87Q(vM@PqeyvaBHMjuq=NoPlMj`SaM#pZ`i)eX-U*zX#V=nL9E7_HT^4&&gh8Pg%Mvej!AdW@*^)<}?TU2l z_+;;uL*UDb*Zc;}`<5xbV-^?|gaMr&g_+RCcID@pr*Zpc-c%Bz6m$nKNMf~UStu)i z9c%FR788>v9eJJU)ZDF#t!}?}>4e|cTR;;zLv2JGpx|*EhXl{i2D& zgyZ>%tB0qT^7@726mb(0`;(n-N{MT)-4klrNW|6N;jw2ZeV(j(t*rH?6f?x0G-E<^ zp38DP5jU{;Ufe;fo_!_b%s&0jDp8@jVq*omMI45qs0;o|rv7|TaGG8}%k*!R+(7GV z;v!$V2fxl|=B2U}RkurPd*0r0qz@3rkob7(#Kyb5%!D#M3`-h~KIEQe9MPCf#FFEg zjV}i-`chE$1$_ucNm~e?x3*qF|4pnG4dxLgsbm53tp0AF@k8ozs(^{-VlQy>l`EFI zW_)Pz&{@1;+#CvXARd37+XnqnWMBE!gVe>nAIn=OkLgWf-KpMCy`m#T(5CE`mG3yZ$~$2Phf{ZGOXE7}=<)-wYZ zFtF?n-dE4K2@l$NdVT0_^G|gy* z%<>2Rv7Lr@tSh2))nwvk`o5v7-~C$sIsN-*q0*rh7i~6{a1PvV0*XT#z2Z$PLa8h} z^XavF6f538ql4xPr_QW4<)pNXB`L5t>zI`Tg7Bl-G1=*2xU*O1Nl!w8Z} zQc%!dLSU@^ecD#>s9BD$R_@2|O4Rskq}_9gnLk$eV^)0NS%@==+jeSQqx+?)b0eZV zjKGsVfHnWW{_VT zw_BpQ?n+x;Oy>Sj*tlzJV69z2dupR6TZ=0bj!p7Zs8VZ3GPHD1jZ7DH^&oBTv12vk zCQD54bv^c9g>{22xH>E^nvtHKPHdhbry_BY)E-LO(h>Ox71^19 zjI+F|GJS-$H|xHO5fCIuku=VZrx8WHk8OJGJ>Y;%tBbdfzAvup!)HsrdOTcM?n-Vk zNsX(ybt_LpnN_{`BeDk~B6#y0J;?|0|U3+az|P?j`|pY8mM!NwTFGod^UYaPwxlV_Mi*gCEvEU9{k~d zd)*1wk>7ghZ5;U>C(MsLey6@Z_)$I+9DbcrkA=?ZnVCbL`(Z}L$9I;R4eypHhkmZDt;OyJ%|i9YXAoO4WZ54-{tP2a zMNkT(4gU@!V*p_?wy=;RI4tZdh&Yng*46`VFr8S?cBLI0N|BnN7ibWTwV-V1K!5tE zsHn)GWFj53iDJ+M>>SPK>g$9qwo2@Fs@A$r_m})ud%p<_n#PrW%2h8Bzy6mg8a!hi z3o5NEObawZK|##`d@44$$1l;*w1DiXE<)e1=PKZecz)X-msh*qvCv=u9f=jaT9+@j z7|WMxpgLUZkENGQ2!c=HmtC*}q;bxr{jR|8?0|=WwhFVl@fj5zQxX;udeQDMzS+ka z85zEI&@C+k@5iZi5=iC(-3WB!?;v1wI*w^GoSmIXcow{9T4*nTE*Pn%f&?U!@PzR% zNqtW=qhdOA?8VSEHnJamz`!v)+8B9`g;-eaiGPHgL_z(z6c;!53N+(*J<{^>u|@CNyJ}qz z4Pk^YadD6NW*ij#SN^RJOI%;BiK_A!Sk`tFbJ7BBH~RN?kjr^T%XKM?FCCccP>u6$ z;{o)<(ZKCoVG&k9eT#w~Usb?q>oI7XVwYIPCp$AnJJVH2F>-yfEC)m_Hq%7u7?#M6 z4oT>-qvE+uq?DAD=C25!dbw=R)@edO8A7*n0J3BkO!u)U;4yDde5M!LpXS+j&wmUQeaHZjNU$4T5fTy_PFLFcT9K3L07I)OH646m z+?zmGV$vT2`g+w$j3$#u!3+NYRaTd)qcJ)zE`8#Z~7vvlBZSgW$}Tr0ow(z&Xt zs?a)+YBkOEo|L5IO^u&hv2Oj}gumvGF{F6V=VgT8}^ti zND8-z=#}_wKu$yJKnA^j6-d%|TR4_O*z4~QM1qFjjgBvLC$0hdSyaRA$ub&354}%8 zNTm)f+JYyXlv`Zv5`F0B&!3nYax_f~D-Hs53=9l=&;uHRZ1*KH@}YHwpsDU^Uovxm z{*5b67ic!yj7u;yq$PHAK_Fe3v?#!<^#YZZRzyT(@7!g#JyO7Vhecj!|6>sOm3{u> zT9pDFiZ}$=K0$2;w=Q=mD0S4;)j@6ipfPAuyB~nm=hRZ(*q-j^$e-o>M5?p)YjU7< zcOX^4nQ9)O(88+%$cB5S+VK$^8{2?$bV`bDnp@CK==bjDX_V1GpOt7`9Sk~?P~aSk zM;zo=G4!fwG0HOxE)HAck)@?ikRr0!M^MT4PnRN=-t)ibCoi>{BKFyys-P;^V+hvUW{>@n|)?TX-#5S&XxBcmeo5hkSj!V)u|Kl7DYbqL}i zwQmoq%Yt2%T7~sYgk&#Wl=-xt-B@p?bIg%gQ$_6%2&F@B2Ju$rlLKL7A{8_*Q z@8deRqvoZ3`Qs^XI7E=ff%C>Rv`4kkw=kZn^3UJvO#FQgYbanoK^%fnGlQ_YnKRM_aL^4yl9tM(iHC)2 zuc^Q}apI?o3BUgTJk9D3V_t_m<@*-MrA#q-b zi;MdozDS0ADO6)g0`E`=+zTJkKjF0=4R$M1AooHI*30M)3l2uTY?;pd`BSB`r=a0l z3X+AC)8NK@1EGd?OCbIl=m^VzT^YI!UcT$Fu`=;o{yQ@@!%*h2$7YJ&VF^0#q=UzD zKj1mIh-a`k67VxeITmUt(#5f^*)C6Ae;Igt1r$l+gXPZW!-@(DlCNLiN*FD-(u8~U zAKPrRyk07jLN4l9zBXtg!C>p47wR>k<*HbeZMvto%td%!92Y?ugX+Up?Yw*Y*^6eX zxs$w}HLb`h`!#?j)45p$E95I0YZG@LKYGbT4{T=D{0AI!qZB#b2Rb8#F8c;QaY&i)cz)>`vD5gWlx_v_r$^g4^uB)Vov^4VYR8i}%XGz@v zWGP6C07l$H3M{sZVvCEH_-vB0vPM9%kd|zEMh2(#G(RA-80;f*+h9?ljSsT#dzv|U z;iB+#U)%a9hgV?B8%jmhUNa6I_#wF}IZr_C1TUB!?#9<>2W&b>;YdivD%e)gCtw0! zkR+hZAQHqL2%xY_fEgME+rYY@4hbqxS|n`&1-7%NCmIkd^$au+WFCVeOIR|&a_iQu zFY#h%5;7<#lvK#G0P&1E1*&3Q&a|uC1JVRp=Eh4f*9GDKK2!v=bT9Uub`6kl|#3HWFNz@I!A1xHksGKk5;9;&H$AUcDTfQYgH3)|88&nEUlJ0SRC1Ib z@R-vA(7DXp6BC^M8Fr&KTBm1%I|`lYASLc;ffeyfqye*Rcl_21_6 zOwd?=!x;n$7h%%@YU{eA(MIkHP%mFWilHxIKuc2B+S-b=3sJzYLq!~xvDKd%01`^R zeM_%P>?WZh63*5;MkourY3jo5&w)d|e<~metB;Ciqgv7cwXg|)N z2ET@ZU6&&zkTJ!qlH16EyV{=;p?@DqMZhMa@H|&i)Rh;@WiR9TH?hznLt*ncueMeQ zX;;hl2_*pk>wu!oJlz)wOx+KYS@?2oFEcs@elNi)EoKN}pB+$O)ym{4>l#_GzQDKF z6*-+pe=RAA^fIC~kA1NyKST2xJiLyZ;8pv*lIg)DoE!jqJO(sk3>{sU1%Es6#Xm`o z1ga{2-`!g}`B+waOwsZ4*RPMwi-eY=qP%1zWU^8Um%-WM$^5f#G>l6~GlyT#)q8g5 zB}PTHXgkleO;_17varBekQg%3HMRWejcmK*W8{wI)Uit(LW8jn->BD-Uiq~LQs029 z*>!{B`-yMX#Sgf+9WXFZMelSBNT?D_&9yO?Zgv%O{UbdS|+)PvbMcxqX7_s^m5WIRrh< zBU6nDAKWst)4c^GP__R4Jp@#z@r{v$`E2b|Fk9E4wbO(CSqLn>O8}-3%1=4KOrZ|% zbuy&fp`wy^adFY(FU`JP@KcV}@qrFoMPl!i69Fvf5@x4F4<0%-8}Fy2X*6D&nR^PT zPNy7-?POBYV!)4B)Czq7cQC&mZWc5Er+R#TvWtI;f8Eluxwri%6MEEYI4YE7l=kCC zC6uoFPwi;om=)6IkY=k&*LZ0g-)k%$dcO(Ej)Z{$4bUe1&?vRxg%KswoRU4esu|1K z>X44TLhKKl_`Eq^qtjhJeUcilvJma%dhzakXiH#xcF!A)rQc!iK(Sd~TN|$TbT7A= z>$|6_^&Dgc7bpUMC(D?jHmrz8i`en!M9n}6fG|r3x_b$U4JU8DeEA|NDf#R8jTXq@ zKke80z_|ibk~~glWMKpnp&!W(pyPyrEb?|L1ok&rkfGOPT&5xr`hZ8q_$t6^d3lwH zgiKp?B%3Ef=-_6>v>gr%gmhDosy|Y8NAeRuG9>6yy8Zp$c+f9=LJm4Uk`=;<9?-^r zrywp(zIh(2BhP$+K`Liv#uywNj7!S3{A=pz1OQ4nLkER)p!*xJUCJDk3I30qkh9RdP?%g9I)!FZjkYaSK6u>D+n!aSh7!04eu&okCqNQ@zYuhs0 z_MNNif}0tethiEHU0G3yN>_RL9F{7~5T6i@TGsfH3%7Gsm?H`Feb=8>ouNFDwvjZv zXOs6vD!G1x7P(2kjmwI?T2$at-W>m+p!armIFE&Li5j-LC%<1S-OBhAtO|1(eM<$g zu4Sn-aD|NhA3q4Urxc_{B$Gd?wwJSp3-lY88H9`^s(w}AN$W3E{r`pK|8HQQE6Ov#_FeP7X8y^dJ=l2ODqmGL+9mjKiTB&>d%vgo?H~dTX@LDrDI&VQ)cH%@t ze%-m@k26oS!1`CO6CC(JcXP$uMcMB#)^T!CAsZXp#3LMDmudV2CFic^sjR2!^Q_N` zQ|)2!oTXh@1m1hDRsIbw}D58876lsQ4TSbr9!c z4HM;75>@K}XokxjIVf==R}Cz>_1V*0>+4}vSI7H~Z}TS8PHY*K9m_nX|G2prnXDCB zP2Gl~Nr{tCZ#}aslX$E%e>7RP+0Banel7VVDmj^mkd-4t>1t5qzptb+AB%A=fb%Ep zg588)ib~1aR$3K1CmQPF$w{H_u{im(e?BDQL1+>3 zdyDFdBSSPVZ@5}a$Cfg(rLQI7TYwA3SOVVi)y1PE(W?TvikuDAyg*eC4|13c?nV;( zaE7_;!&k#St3(PB9pA5lz29r4 zQi5SSScmRU)g$kG5JN>scp15xKq40a3Q(cIQ-gxGM*lC^S#g$&VD#_FfbNd==Co;DqpAF6s+N{ zZ#Ix3W@9*6Uq8kE)Wi`~bD}|RQr0p}eXE7zaZ(k^Zas^^Zf(L)2J?hypSQA*+g3*F zEN9YUmGo1WHSQ56K9jD*vJ3U3-=+Q1%E|2^cfX?P^;?E83ZUsaiDZPRMr;15IicQR zt=ZoazWMCbM~7BKkieoOAgJ4~%$XJZygFpX6w@5(cAVfSn89|WQyL3-6nf> zK<14V^)}x_*NXt9Tt&9%iIn&!X-4f@G~4P+9JLtmP!y*#2r7a@L|9Kih=ewdsR$g>+;9mGdivFk};+@4@ z(w=<{mWHX(fzI_!iHhTKf%4R*B=3bwSBf)5(TwE*B2gO5qu-+fenV6$MTh`zz46?1 z&jas|N4>2{2|*|GRC5R8o=vQD3-**=e)s8pJwqs^}4PIC@n_z+k=DYWF<0PXs#ec^**Y^CSj6uti<)>!bJ3 zvw;X)YdmF)zOJlpc9hv4RC_>+`sQ@M!q@v9LwM^D^({pYhbl)1JjH|Uele{f63vU% zm1bCBUMF1#!MCb-)Qey0$7{+TW?7IOd_%Ci7I=Kjdx?WWE-Wx)xZ?d`w2b#P9(FvJ z5o`Klffx08^+V*;ZaTv}quDh5*aaKl6y@ai2w#WcO-eq=5OSB!ZQyEydg0bY%lpEHEH8JEqXSzq!tg z;hU9v(@IlpGM10+U+R;UduV#XX#^SF;19F7lpOADiTGqZCyb0edgQ%fNyEO4<70Ay z>M?QVdw>^$fs3wKi7wGEcWuPG%cP2b5zmVHK5rt_rOxH#LQ@FxPrq#O+>;Dt^`jcL z32S4CCp%Pn1;V|pN&J{J-FuFCQy(rWtkg6!-pPeaNG1C!P9;SD=iPC&SU*~zsxbKU z-bcQXdf)@o*|7F3P9$9}c?}rCY27aGU32-= zu6?jcEaJ5hOKY<^efhX2{N&l~wmBAN)z~=A{MNy=G=%>cma(GV#iGMoArk`5ckE4H zzKQ$wU;DFH+1Y_>+hxdF7(|u$^-6`iQu)Q9zr_ zV6M%+<&0nDjASs;Nx=5C+$o<4>Sx3%!`zkOn2JHYGlitZFUq{-lSl^yy;fqP}MNZL>vvu)l?Xu+?$`)>PNlk!cW z>`z_#8&K|41lEmRhW(HyS?p#%&?W6;5slYKfQ=cxnZ0-G000 zzjwmy4UOdy$KkM6x z>lm)thKEsBSZ!-!5wu-{}F$J%=_226s#5;}i1^9l{ z=O~*TV0-4yF4GkJ`Y_yllgPx`?iAtu_pa>`Jqe7s(yvYKiz28EL!uKrZ2Bnv zARn(uZv8{=QS~FbR)JeD`v_&qbD(=l_b%Fd*SVX0rsS7{4T2Rx+E~Iab$Yxn4da4C zpWxI9@COC&jjd!v9Cor31{~vLzItRaaQ-SoEO6{L^v!=F`eUL$t1hR#mO9%-P)uZ&xkZJ*NAL9uI124IkowaxnJUi%o+OXqNxAikI1ie z>N>Mm)j>ZLs5UEH|4t97tZ&aeWqDB2Iq$7G({olIr;Sx>xONc~!TVN?#pBTaR-%(@ z;Z&+CI`b751-~Vxi0`5T;`!Msxi@o>HaD}Mdmxp)Fv010CZW^T;b&czQ;&fn|FFY8 zJ96i`hz91xXZaC72oY(a3X8o-&M|&}K8k*@pVGlU^k!Vq%f}DwO?7uCo0%MsP2Il$w~zgQFqnXlh!G6_YZe!P>r_9kY?&l6!xUelbl} zEFeC-h7zsedKK;tzh%#6%0T4$_|AA?%S&rt{G7H%4!to{d~OorM}Zxe?Dg21b%*N^xGip?EeHV;`2)p_BB= zSk1Ko!fd<2g!r@S-*?V2fdQ|H>#xdg5d59(lbh?ieP{19T-V7#tXH#la!~*dEuPy& z?^TzYuv{E^Tvm^S={0l2b8w(}>M7%uY+nd1-#eEK<0JC^n-ybr%lO#Pbplo{K)9iU* z{6L4{YsIV^@}wv?Jek6_>qUG!F@76shDBpdjN@IL47K=I>rAz}K}2^w-&FU~upfT$ zNa74IH}5W^I~4k#BaJgm1j(E%M$qgR_e_cN9E!KR<(w$@AnjB_e9zt_D=zguvAAD5 z^Y>lSbyCMs4?RLagz9uTqJdMQer6<3(FoQcd~$KJzMm@Lfeze{*o3h5HBnPq3a{7Y zemhF-zai-)ur!d~3p^0UkvoS%uHsi$rL$UKyKo%O>?pN0R zJ@e-wX&;E$YfLPzRQTfaw#$ke0!uV-`*!}$cT-70A$xV=Emhlj{MciAN9_pX{z>+#!l>stm|rbsK;J3>pQBcX9dXw0ZTiKqWz!K$!?wNH zIs_42q+Fe+)mJ8UlH!y*vzR|AqWrvlmDd|bVm`A>uE*Ffl0DoSnN7QBbVKNaJM$8EO!slli@68cc0X3O!@WQ{vX7T$x=U!i;rg4d|* zG(r8+cG0n3dH!%-c**Xp1z^2!RQQP*9OtAE2xUr9iwd|pJxG`OL_&;Zp%R)OVumzVgQ>8QjrC6+#OU6D3C zefI?g7cP1Qz6gRb@!Et}A4EZ+>(eFQ+ZSMY)Jd(tnSI6OKL!3tQUXsZie0`&wdEwW z>)z+DNz*QPLWz6`4z~rRhpo12uG2-0x$^;pCLZ*bU3_DO=QlXX*WM%1D(RbIFjL$-p}| zIdUjUM*3n!sg|ob-+^VDqwo~QzWcOoZez%c{hmR#+cbFus63^TS-%7I;O324c(In*kjkM0j0d9)%!rX9zLBE+r(6M-@GmASvt{j>Zo{2?8)Ln zAQkT6J4QtXN{uXH_1D1Q<5{r+ZgWKB>;oO1_g+}s(!7o5 zGs-tPqHb{$7oIiX))`pb5=GSG<*1`HF?{Hz@W85r^`s|KNu;QG^g^CYxx;iO)%pX@ zlUnoKF;ZiV^!t|63|pN92huC2EeT|p#wGV!teY*%=gVC0q>vCa-B4q$WP+**dx&ro z-;Q8QSF+;3MJTtG^7|1pisz*;QOdRC5YtK|N`~j3yU~2;HxXTZ_r3dm;LavQThwcL zvai6*B?9#IcoQSYv?TJ=qKEbb7)_}`?{d5T{y+VE^Z)7Ds{gA;sNrGhXP)CV?yAt1 zFe<;yG;+z{HC3Baej}yavVey#Y^C)7P&?BRSfr_35B5lLE#P@`l-IB1#PVP0efVFv CKuMPX literal 0 HcmV?d00001 diff --git a/docs/docs/guides/05-using-server/assets/chat-example.png b/docs/docs/guides/05-using-server/assets/chat-example.png new file mode 100644 index 0000000000000000000000000000000000000000..4227fdbf63d760c651d894ef93725e732951cfd8 GIT binary patch literal 81919 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≻ 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 literal 0 HcmV?d00001 diff --git a/docs/docs/guides/05-using-server/assets/choose-model.png b/docs/docs/guides/05-using-server/assets/choose-model.png new file mode 100644 index 0000000000000000000000000000000000000000..08f468264df3b270eeba316166c1ca958ad1f1d4 GIT binary patch literal 94823 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 literal 0 HcmV?d00001 diff --git a/docs/docs/guides/05-using-server/assets/local-api-view.png b/docs/docs/guides/05-using-server/assets/local-api-view.png new file mode 100644 index 0000000000000000000000000000000000000000..6d5b13e6f593c8bde45367d0c3fbda0bdca2ba5c GIT binary patch literal 26208 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 APyhe` literal 0 HcmV?d00001 diff --git a/docs/docs/guides/05-using-server/assets/running-server.png b/docs/docs/guides/05-using-server/assets/running-server.png new file mode 100644 index 0000000000000000000000000000000000000000..078806d88f66030b88753f7b554ac68d24a6d58d GIT binary patch literal 97561 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
  • ?*<{8 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�TKw8kE&9 zQh9v%@FpB3XyRkFjZVS>#WvvlAG(ytcuJ7;#yjyulkU5s!MD&O zDRKKXQ=q&1OhxGWi47apR))mHvc0mk$}J)ML0WKmVkNg~p};6T#b##V$R(4_L6rix zZ?i%Tla`x%w=>Ww0c?<4!sUtb=K@>=D;gVO82PP#Qea&uI ztCf&1667~-0i|eam(Mq$IgYYn)^dkxC7P5V6KRxI6%9L20XGf0d3(7z*HQQgEjU2f z8?P4y(Z?>V(F}(*_Iw{Y9h>z`=7}tY(+}U4)*zA{#=p(T_5BdoD=zhk+>kL-bt%i- zbdUec%#4LA8<0F>_397@67W}j0t2Bt#?UL~WE4aRbB4+uFUG|raIxGIw3W&Jej|4_%kwe{wHQ^%f|Heme!q@pQL2WW!>gc z4E_9S=tH{6#(YLRn3H14$tfLwBV26?2=ouDAeqeiXDK+mTn^BcCOd+jokM9u;|7Z z+vw3`oFkao9kE7teP?9qwL9ql7(^pkPEMQg>ee)UsUG$%TOLD#%Mlh&jBoXjgaiZh zuqaEs=u)sh#`Bgr#tpaa85UD+)5K;e_G4&$=EbK-llc7EvuI}5y65CE08C&a0;hTe zQI#nNv|BRXZbyj~`~3MT_MIQ5FHeQ8+h4P>*@8fXmi-gIFO}o-DhlYD z$JV?L?Dn4(0j!u`FFmI0uex5uzh!-8fN$eu@!^>>7cQU{>?0d@H(uTj?e%DcOlVNq z0dhJ{Q7=(sP%;vwo1BjfBKS_`Yz-(#dX@ zOJ=8EV&(=;@z0;MD&r<|-Y-p$;iX`z_|`LrO}?zGe9>z?cVMUDCwBlgzTW#9bJP?R znn^H!aW$|*XV6^nEzw&vsD^4$>_I@vv;x>N`hM^LF^R8ym{)U13mrj0~8dS)L8?+m7g=& zrjvgxy^2OQ=4!%1_ovn+IR&e!BT^JI(`I8*oI}gujW$u`D(k+(QI*`8sNHv?A?%Px zF;#Y~-d$EIPKfCkw)|+5p!+%}y-;fXkF#$x1N;152lqzIa%wc0)!hd-^vcfJOUcLchg!(o`r>p#d&x&`BTGwC@$44^lD>_`tbw}q^npS? z#mx6=<-zq6aU~tovv5ZiO@eE=mulj;f0S)>iUTD8v0l6>X-v5S+9vV6-(t)o!JDrC z77r9Ndpz$UbbAdAg@cT1$z@rfpnxBJee3SQ!j9Z+0etKHgCBqW{D%v`N@ZN3y$gN3 z5VKB}9RQXs5LP2DDOnbium+F*8CD*GftLh5Pn%6^?o7y;wle2p*y{4~tQ0M3L3_P@ z#Jpda5I84Be_!xBuW%I>7#efiFiuChXtU8dg020t>+`Aeim3;Hy|)|jzZ0JXKAR@n$YK9WecwJ$(@}Zm`vZBZBO}mG zRf_X;qCviYshWFcH;YbHY^|0|-G>o@u$KsLeNe+uo3VNPk!I?s($-IBxKKF!#V{j1bYw48Et;WOX~$VjTv1v!e9`c@H)|gOY$onxejigRf82 z!cd(H8X~hpKMiJ8m8&`NqpWa}K&*i$9P$uYF!KJxg7kB+WZVjeBj5}S?>9DE;t zpi0T0coMNStNvd@L{vL>oG+iq<}=9IFpz56O1kGxk~~uo0|X`rRNe}LH$I@>&lGH0 zXnR3L1VjK$2`A`lbS|vs8?-8HAk-89{Y8isjPHpOYuXR1xcI+)~HOU)Zhd>09EST;+6ua*j-M^);?WQhg zeCbhUgebY!wRe!QdJZF62U(-TiY4gA1Y1|(FJp-uLh{Aupay`kw3@HNYSyO zA5poHt%L>W=MhK`5N7gCyb>4#FVp)D8zmVx)+CDoFOL~ZUNk-+w59sd%rU@&ZT?s{S$8XIV0?o#Uh4^78Y}yBayV2YYwEq~f|sUr}B1Y(KTyx)Se)EX+hgj)wSA z+|Eveo$^dYHQgoFA9djy_Q^ksVG62TS?;6;OpNnV@_i^(u3e`&IYCI2w-gaBW1M19 zn=}1mx>%L$mxywKJ3d6by(>*|4?I5&@7f^*WW9(wfXU38g#W#T#G*!jHow%RIa_#j z-BVBC#<^!ZWhC@WB=t;8@(Mn4WHXq1$}+#t&bpc%dh59x@V1ybj&D;&vMBMyiF%dF zUllTy2pdfGE*PeI<$6c_)7KGY%7X!@vnJs9*G)gdZSk==q1 z{p$Ol%=M{~xR!jHQRo)BTjSJWg`ES9$ICheALM&I|0qtLRHJI(k)CK2`Z#J+&f(Lk z$%^M+i_qHJuO`e%8c|Zkkv*A~j6LX9GLR~n3s^V7Hud4**qqqiYWez3NoYyF=(KhX zJ$f!Rx?hIO@1`l1Rx@JC!lCzJrw+eu(YG=UruB&tO=-TZt$h&vNhjDpZE|$X^Kb90 zuBHeU6J{95MnuRYU)!n;y{ALSZi*04@hZX7Hr?OOKX0# zGpxp1&9vQj82y4>RhJ_nKYK&AVT)|s5;cNSbg%~kmRw1m6Afa2hTFf9$wZwGZj#j1 z1>?YyttK`Qn{28E+@C7&9S}}vwtfmmeah31L4#PDPRDGIM~^fRalj^TCndd$ObBsH zPxOM#*QY0t`M-oWClX)qbs+`^YMt`^Q)8V@PYqigiQxa~QzqKmhxjoFa06371v+z) zSo*xKJP5i8faFdvIazqWrqvz+MQUNvd-LQ$H*kHKjycS3KzN9ffkOV85;Jqv{zHcf3LZFM z+ODn-l^`HBUN}<;26tpk9I!W#r^xolA;2#`kGC&t^8O9Y7@fc$cRjZBW|k~5bXuEjGXCcm!K)jXpf9F>!UjFnsE$!6I(iT@^<8rqzZ^%BC8B)|b7 zW2s7FZ*Pv^><=&q$;YR$u?7o1KOkB+u(G&|dwsT~=ais(~QHP|lwq#xo_ z$0ftVDH=IFn&jnP)PdqVo;AW@KlzzXQ#%Bmn*NE2->F98N~2};o6{!q zDI|7DVUAJz2lc(W4!ISPhmceFqlf0;t6Su!1a`;QJ4~_yfHRvG8qZ&k*Bb%$ zC)>lzV`r&V^xPO=!1U~&{GZmSr$f@4`Mi8wNc?qH?RcSvqq@1dL(Ny!v<`LE|5`pX z*eOx@Z1>?x7nw`JRkp6Omf7t*8?XNefhfCCx?L7Ppy5+=bk(ai*_LvlVncoYp@NZh zh>kCrp9#ULV>{Z+H`9?ox0eiqW4gb;vnGOFEF}ph^^sk?C?TVIMo!Txs;$j!#6U?) z1@izgn^!(g*6jXF%w|D~M!P6PRP$q(A6cuzA+j!LK_I+3Jc+-4~e9^R)n6k8usL}6%99ws) zZcS`eiS(HRsRoq9xCa`VaHRcH)lOAb2@5kbeNg%^b5fDG^Y;Chu8xJ5g5!~BXaK^9 z$rBqPwS7>AQ&3C1K3o_C{7XekbDiF=%)Mb2>XJ0<%F55REt4Ml-ncq}KS;{UU$?PY zRiv)hV`OGl?(7616CBFj8C)48*yAJ^I&m073GYwHNlUu}t4wR`0I#4eccq9T^q%Af zG2lvXZPou!HH=7wDEG-eYcT_b=GMF8%pbWgW?+ny=Z6J?u+d^c+jlbf(?qh7Swz+H1#|a z`=JQM)u6Uyt$+DES%Hz&7Wu~MU4`YsGJW!L%t(x&ds3lfTv?*Y3`Hm^@D2IHm)Oo= z%!P@sXK0stqYoJzue0hzAxJ8qtewXe?hCQgMtYyEH;_4NC(dbJsU6dyWl1BK#phds zts|UvJUVJ+Bk7JEw0!v_G4+ZgJLb+&3z$q*d;AIUe$vX-1$8P;FGx72EG@H|>c9N_ zW^V7keYXJwkN|>t5+>+_S*if|+z^& zh}Ixn5rSk0P}+Fr#|!BISjK+_Tdj?+9b)~+2W$?~-Bczbd5+V)_mH3i@<=vlZY058 zao4W=qVF*F*I)65jLP4-D*{r03%Uzvs2Hh{BY%<^&dbP-mb~K32i3Z*U9QxY=~j8-IG2H=Qu_2Sei9TsF6HIf0(~MOn8jUzIY%+* zY`dBo&xQ^0bd=~cZ5Ub#sYNKHk}V z)b{Ys$AmalR9UXBd%Fz1FXm@@0?~=_u)~ zJ#xm<8PaA?_V`AdK9WIB`Y0hG1hvIXe}y<%fSn48sk)-&eiw75iaVo6VR|+|vGEFo zkihoohp1_+rE*YZF_?HnRT#1{#UVV|3Cn4$Y^W88S+!mC0IDa2^c!tPvI-n#y?bfJ zj;ZxH!mp|?&r?Ity$8FV2W0tB;4(Lm89Gv6DncZ{+(@@D9Rr0DX zfx2=EeqnJiY+Y6%vc{SPWF;;YD{JdB;6i~txWE3%PZcrLQhQ6N7f=VNuHPQ5g^W#U z5$aH;CK3p&k?c@%^gUkYnEEnwT%dkse@ggI;@SAQ_raPoay&hucLjHoOioJiOY63; z4a_ZW+-7)+n{S_1_ZPB7bm_-1thaWm>MTxc5i`4ypMr-*h6?D+w^LqF94Lf_Koz(+ zh1;mlt5*ir6FjY7zcv$^P8orRBE-B3)tO9C%>3(c>oeenZCx??D&sF9?;V28=SM{GS@B7Fg-QGdL*$H_2SC; zF+OiUOHyNFV*tjecjS#qZ8`k#R`}h~rkBK7hbSDf<=v1N0KGa92q|zZQ!c~nH_}Yj zCBaWWjlgCXMKbO~9ayE1zS(gll$S^ROjoOMx`ed!ZC%~%`)S=Htc)8OcK{nKBBp9Ro5p)*&}lZeJ0yBMx-euMm8MsP`5KGMF=_E5;G? zs8Gulz8yNQN5W}4dj>iiyk6YXXF$m;E)guj!$I3Zi0ulApBXIn^wLPYXe`9aJqikr zHzaf(KP3@=r8}o+>tk)5;K<*Mo0<>gFKr*SlRJ*k-ovf$u4c#d4#*K`LHheAzYgDA^W4YbO_Y*MK=we& zTckdtUHrDiLs22O#NEsP@n7xrkPClxODmT@1|ZI_Et~6Y_&|Sf-muBK$Ae?AGoKGd zupKQJe$s=-0$iAZQk!tf!d4q6a1T9pWMs zT+GcsJd#O&osm%rfF%T&IC=y)#@|IPE^YxG@$?0wWOLa+sD|mT=uvm{o$MaFt5bJk zSE#WCv%`!2mQ(KcRPR?Q{mpwkKmFUw(nZD zsOm;j!$Q_o7o#3X;%n4L`&PW3I*i)snoqrvnd7~OZ{0Sq>&PhIy>OLB9`D+H8`~5t z7!Y|E2vR$I?Z{o$Q&y`xp62Gihm!e~oa!PA$n5=4s^uYybkg*+B4!@k1Qi?@C!!-v zogHjWp>riY`V)-pR|UiS0IqOlFLWq`T)4{044Q}pZ@u^*n5mo6_W`+cdOAm8mSQPA z(|xX8-c?aduT@6uSxHZ>jNKmG!Eh}{JfIiP%&WD5(1$8lE46NLP>*hx`c1-C8!5rf((Lq1-r4SphVCMbVo4w!5Sd};@=%) zY-apzj*aHOfNt+Us~k~0dtm4LLYdU}&kZgGHbT?oQYi#I04~|_fvRwn zcO-%G77c_lp1{BmlP$7ne;)|2Pe^Os zv>4(R_uU|)VSB5?cLkD_?_y)$fJxia*H?rhv~}-G!S)dh=Cy3TpU6$$+)QlHQi5w%4?eLkiXA8Acy2r*ukig82G`c<5 zw47OTKJvqd>2@o=7rqu7+=^~pzkbMN>z*zYk!m8v#4N{_0${C~9G1rRIFkLdsoIXb z5!LYtI2?mii$zdKU4~3H=68$+6gPwR$+`3APhwG^%b8^~UJ_Xzl zJw&->VccP5vhV_?hhOo(5Ik5L?z7A-A@P!kSMko+4VOGa9b3vDY;q0YwmAZ@Pa~!I zsk=cHrY^Xn%8vGzuzI}>K;7O0AqB358sTo(qr3*=s6Tnt`-9+0FOwcUMZ<A%gzbeITRZa+4 zb@jTxbel``P3k?$q5{YE$*j|RODoD~hBWDIw%jlu`tc-0<+AGi+~3@yIEhD$BF{h_ zQR-PN-WyODeGI$<63gshA37=I`G6SeqGfKU8H&)dPgnYD@EAesU zpujoW5qia-q`{r4#Vcp!q~p?JGa8yPxc=?SQccwwk53QQK{xn zEGP!4-vGW2p04GDQ#B9EP($2|OlZ?Wt#I{5(GKe#H5_MXRtm+)Uc@Lvc9ICO{NNM( zTR}8t2Z6z{x#s|d|3;+myRTSU#xDn+a|svj>&^Hy^!=S-n#Iw}pH89vo|6OKW=hI^ z>yfxc0&!tXAW`os$RmNp3vyT{HC?kmh#6I95diI`G$Y1qfz>1%mGI%i?DK^ie}ak{ zChYFg$g0{HB|61)bC1C@=jprUrzyFt40^hUzs`Lc87TaEoekFJPSVQK+U&1q((g|N z)r1LeBiTA{k!&%*OHc1UIL9R#9#NNVNB92JDU!j)m|d4GsqdS8rc_D0prvn2m;0gTdnOjc}TTh_#7Mb(Yiv!Bq)S~9C-yx_laqwve3D-NEg!Hq(IgbEBQ3UE@EV%_%%YG7{ zQt5EjKYX6Ab6P7}OWtlSXG7Kc1LEGaTyL{?r)AmJ<8z2B(JD<~D{4zccjKl5#qDgm z`@+rgRZo6-$Z5KCUcd(+j*v}e%uvDIG}IiwRi68BBSl<*bg2B}oz=5v&UcrzyB?mU zddSZ|H9h@BT=jX8L9tPKIn!0;MVEG?lDq60XBdl+=5&4k9y4L>a?@3Y2MdXNeJxV- zD2nqpBI~l0DD;=zvJw*yIiu>lA3hOW24vS%siMK6ZXlh56oMW%FOWLIK71H`z6>hE z9p(TulNEP%dZ8~0Z8U((?B|;)kjFfjoMgI9yFXGavKAeo$W9c(_v^t+j|(-P43vrUEx2 zRzT=+UTtlU^W~cx!g;T+DzZ#hTi#F5NK;FM1Q~kA&&ljDX3)Te~*%%-8fT-aRF_=oW zV|=nmf@(YC^67jKOE8+Ks=M$RY@{q0G-VGq2{xgQ^>%o54%h`z*+Lg;`@hbEk2*NU ztyq(}CoHB#f#wvok*ZEDbCRMV9LhEmuimq*IX+SQU3P)X?8t_U}IS-a4wOiF6bNJw)i3oBq|o{rh)y#}!j_)hd8{g6abC0#}@43hx!W zp9OG46#Nkf77wBJX98+Zk7Hn0A1FM5zkX?eqxS;zvv1$}ferXU`UD6Jwo;@_jBc?P z^<&Y<@SL27EIPID?`)#8e{&;rtsiV~B&@a8z+XoWKz?VJ&9B8_t+nW&rXM>BO_YD! zvN+wnSvJVy>Z)>>?iYR+IM*;?x<Mv5Z>?c=8KKnp0m-iW?%p5XYs9R)SI*)9cOIGfZ*ig&pgm&D)-+T)DFM`VG@f zHx5vIb$E6$jZM-2>jyeofrL@i@9NnrCF|)|8lu} za{I~cJ8zmd-Fa=J-M_G{4^t9Yc2AP4C5)8Oa93MphE8l|v{74d;9F>8VFOd>71o$t zPT6!S4q4Lmyxc!IJ63ltf3=;ZIZ`e3jcHlLvt5S+*|m$F*V~LO z822t_?y8Nns(xd)^eig6C6&5Cibd@kOLguGA-bbMS%HDvvrSTz?3#7OBRSb)qxn`9 zi7n+KLGvcZYzJ#l-hB}f;n9q_=EK9oXb*VV%AE&1U?9X!z$mk?q0Me$HFG$cY@+|K z{FMHFzcygSQBhH)0K+~-x^5yNnK;{1>@F%Se3p!7Nt6B@{rw-^+uUR(O!-|$gM2&> z`$UG=ojA<#Npt<3i`n#)M)^dw+T^W}Ku$V`<=L`}f=2`SNvZOR`o}a#`7S{@qvBLu zsHnb@zJTYUz@8Gr<%yV_^w)RN{DauX-f=Gtx&ySNZ_ldN8T`I#wzjCF^XwEDHNt46obHvx3Hxf7E%UkjPTR%oG%#zI0 zrcn4#*CscdS8MjO5E;9t-8QI>JyR9<`0_PgaddZ zY4q7gHQ(8~eVW&-d@>)~{5^0{Sc%0!RG9XN;wj|~c(ff`p9UQdQwnGG`7;)!XV^t#%1cE&&wv4;r>DYWb+$J;||fB<#M02YG0(T|aJda2O2ka6Km>;DcKdXoQ$3FEZ0#1<`LTr49W(ak-}K`iA;& zo1Ws-bgQwvY?9X6wzfAdSy{iIxdUsOrRg&F+2Nd(u6DiBQO@^VF3Ymol}{{FVo6Ax z$A&v{PmDCi2}SizkQKz&*VcN^={J6`TX+(j<*8o3gOOn*Fe=q`{A>EEA#Zw2G4AbX zuAZL0l$=_qg?^(V)%H>5aLKbH4e>%M4i0*HRz{tMpQHEtCA2M87J6&yu2=D`RSsCz z%i~(PN)x;ebqy_nY%y{X;YIT&uQcl2yI0!67K2~xU zW&F=2K^~q6Eh{UFweFdVRY_sTXvTZ``YGw;-_ORy8QwE8ssbl4<)=rZ6tYyu)n%*F zw9EOldvH;!V7zcdcqS*N8({mDJv;v0Mn*;^DlSGCimIct%mp0#W4YeF%X=@( zuA-I}&){HZZsb=VZDH7#g#BZFUBuAP)~Z|W{D!PRng_v5iDHf1pX5|0R@S6~s>h;< z$B-O&+)}dxThyY|qW}E(id>n|-*25n{J=vQI`?C;!6EIIj%Pb+$qfTC6MJg(`oBo} z#p?=J8xh$-iQ` z?#(6+yK}zP*XC%y=+Hx2nHrdx`08?m@ojJXwq=x_c&UUB)}xPv1dWztJ#-G5J2FOB zM)0)0;dB$`G?%w{GM{jJ;)w`@X?@nGoM~I7{_`EFyP28~#ISc_mN4X!PRWmb5E5+A zRu3(^9a`tjcDTiASW#FmRcq*Zf4W1yPWtoPmr{!>{#+@--uyvLAv;5LP;SO6f4?;C z#j;Pa`g&rmRdX@Y|MmFpkim-=*gf1>`3I>~{p#ihyF{{4E6#ac)c2##4p2CtitB55IKZ2+B z0*it!Opw|e9Nsd9j_i?mJ7%r2O_b~g`SxowjpOhBvW!DBz*+cI1`O#Cxqn}HzTN7I zDEHK_Hzr?x@$jUMEtQHnzNBOcsfdaUc|&I3mBYjH#xQGjJ{y>*$Kp>4J{eM(p!y#_ zG*^Cwu~?4&wOHe2REoDtL)f&$#*`sjI`^Y?(%zcmZ4`CTK`XMW=DmDQ$P5jtWAmnC zJJC{G4otp~C=w~;JMzO{t2$WR!~fEdV=bz8=0`IFc67wd!bv}!YF$z9?WJA z3yV3<(L{;Nu7@+|>~m;oZ6#i`zlefD6Y}WyjE$>JOFV?3<7!_uH*Rev zaf`Jp{#yJdcwyv+#riL=&d%=8ft2d1GW10RHL4j`@Gj4mLjwrSmY!$w^Nre<+DuCl zP>>#Fh9tSQbr`%YL=PR<`><^k-(^69>W$h^D@C=BSX&5EsSx{j}+Slfr~ z*siBtk1i7L`T6-bHvJ!__1W0iIAktdc?k2ME$`AYa@cz-;ny4M!ZRM z0LRHEG5h+;ob^?l>Gi4Z?+<@&+F095~wmBke%X#89Q;zzeI70g?+BWF>_ zyO8wqhq}hGkCCAv0n{s9{n%s^QYtFJ(2xtIUYn$|8IIENF<2_jn{ZrMM$>+zq=G`g zRhO-ZJbfXiBpMhLggK0XD_z_8O1){yYZKHuf7I89XJ%?)YHLeNAdJbkrX)dwxeiL5 zH_10*TMR~Xc2;lrlQm zSb6sb>aQu0a!)#QPk+-`EMjyF=vii7w7aXR^fiKSzk2zT~h4hMiSQVW?96DAxLIps~>7iFs3>zrwK?N zfu}V@qWbVk+OvcFTn_D?Su<6oLf*SBT;hlfX1`b$!ShOOb(n)VID}E^gPp6K?#ma$ zxt_sNaN)o&exZnG4DD>nV2+E610h$CcYP7Dj9hl%#UT$4sydXkl`7+Jd838=gwUtJ zIaw(wpS9J6(!<8A+TfiWUCY6(m^3;LKaEcjj@i))Nq4nEY_w&!l67zx=hVDjT3R|{ z@#8OSLkF8rbqje52}*CWIcan2l?sdcDfvMRsdC=1nqt2=f&U|f&4EE+v7XFpc7jfw z_f(Hp6--x(Zp}GtIWj(06#Xj`9Or^Sw%mo&D8$+O!sRk|d{$PA{rLUR3KT7Tv5%|E z85ye#)7t|SQu=#`WB3&FI@>bQ9>KWQcW?j$Lk=rF3g@%05((9>czuf5u!Y}dZIObo zusA_3WXA%I@laU~XI<7CYq99lFTZIhfdN za*Vw-+cGXj#l;CJwNh(0-wrgdJ$Nx%EuZ&jUT6QF4B>D|EJnKB3UaZ~Cr`!$nEM`7 zgytVdzWt3{P8sX5&QH=@V~XId4>Kn!r%I@U(C|GFx;E6wY&GttkUHoqEo-DG16e3X z&e`uS>6a3WJFV8LYz>t7rg=*?eW5DSkU4i2j)tSzQ>B-K}BS;2gGgV}Iud9=>h)pHv@vDK;1 zdUQveR+2IYLhgY<+6VQg+S8>w=-kD;374SL@#DvjUT6>8M2t@a6_`k6KFAA`Af(ar zoY>TZ-#;UeMO*s~zY7iwZU=LT4sy+6_ek##+&y}gfoaegc?u7#>9|;)eSdwGOb5N7 z=mWX!TAYUCL)cgW53Vy77HMd**Fnd5T|@3kfnGyp0WV(7?2Ef`oH{Bu#{^?8Xtg`#ed`eW>1I|A4xQd_f5Cg1EUw6VI$A-j=*sD*_^74LHD=~6PMUp_>X0J=(K&ZuF5^V-p4jqa3U zeYlK>x7o*A>_s7HKnVZziB>9t`vqb0>}P^Hh4}dwSlJ9KPPiU<6BEBQY01~~Xh+Ze zk+^)qQFwA{-?nLPM>1_SoMmi*J2dclo0HO-10 zcB8h}r#UBbQoK)>9AROHg%!bOuwNbNY#Gji`zhmFpUIDOT6c_!jlFe$CL)wSlsDH( zSD7x@+}%o**oWYSjjY^7&W}OZT0|*``zU%znlZQL^W%$sFawn~fNQrWq zx-EEKHd5xDep$|}`#tZTp&>@mmqC`G!_uZ%8&f$;@x7#sOaK@IglR*>a{vD4EdzdD znS<}idJ!UsKuJQ9K27`U)76=PYGoBw`cF2^ZMEU$l2TH^@JsPWQn;AoF}C)NL>TF9 zEEeHT;o~`nKNjAvWLswd8W9Pf9J*LRcOQZUrzSk` z;z)j;WA>D(V7ue83K8N~anJl<&{j%AunFctIk;t^5QWJd(2-l5*+r5=v1oj}EBBlC z73*yTh_i=dMTPKqDk>@{6=P;0(;a31qUh6^lV4`;e><^l;HBWPa)$JkldH=UKALqL zCq0pAT*#TUXNkdkw65M8+jZ2S>H8}eQm*5~F^23aDsAiT)K0f)MYy8MC(>*()v!G~ ztivvrMi}o){mg~uO|U&A&vD^(upMa8PiTo-h>eae1Ea-qga^_7+nWYiqa^%Z3JAw z4DhDGpsDoQ+(E;!(!f1q3Muc{X8b>CFTL?Y+9{Ah;7jv*WhJ7v>e}~16vT(U_4kY` z1@ODd=;*GO%^EeOD&GG$v%P?v^;6rj^nJesgYplj^s{?I@N|+JNO2#na}@D;P2X=58T9{Khr(2GFc$Z+BK`v8e3c@y2ZGg%os_$#TPAmTfegIZ85@_kjsqQ^%G`P~!X>TOwLFCkL&-@F-4L^Fa^c+u(wg^_M zN?&Y?r`m-FT#?u=`yWihKC$IL&-~#jdWiF5v~WC&j_JL~|3DqMR+y_yq)vLU+VgZ^ zX4)4#~sr{S#wf^lqJu(OQ$2Hd40_V2AFNO6eApl&(`5TngK1$|=cWJS!Ujw$Eg z(z;PAuJ-bQ13inGxUV>|!Z~zjj;rY2|Gh2W&iub|aQ^>BLH<{vFaP_x*gpSrHuisS z6)fTZGWhWSdIN{joP%pPJT7$oDrN=ze|fK^t>(X`jj(I-*A>6I6$e;pqU@q!IEISX z*UJpas`($=({4GGi`61&O5r*Zi!wv3d>SnSL%7Pt5JNp9)4C5T9z8M zU^U!UC(6gx&!*h zb54ETUzQMT*THF{HFfTHkU^r<#PFFInX$7o|z!9eR`~w%16HVOoiAPO# z)b)Lij)#PdcKk6sYNfuKwj3yoDtKY>jeA+0B3XSW=GNJ#;yH$dFl zxV)<0r48p?<;ciebGn1H$B1?Q-p7@5`gg6cWpT>`NTrAT)5L6T9u!Ij-nkwd`zi6~`FH9p z@H+obtFzfl54RmSkntWX*9yM0WlHa%0 z+8?h{=FLYu)>66;l_BC^PJlXbkp5pa-O{P)>FE;mZW}%A3hV!)vn%mx`s%jtd%o88srXq} zr4@mvMo|<{5okc5Ewvg^sG_1oh*lYtAxr`RLaeq@OBH37DYX_92S7$4KvXKD5M>ks z#3D(M!AL^DKuErQY5#)fTCBxV$uH#Id+s@V@3YUgdDxXI&1?^sj+6g{kHX*l9>GlI zLmS24S+L|uAY{(>pN%4~G_Wp=KL4ZC!R1n-llyHV|MJo=)#PI}p@=Bd;olat z0U{*`JMhSje0w0f5c-~hZ&Er1ZZ|pTBDBI9zsO(t3He#se}6iTKkn=o9CbYh5JuCg zh*$ptGP)D*BOpX@r~K_{i<5PUKy#h*tEcYd8yvtK-~zAbSLecdl;mB%r1DN(?2I!r zZ9wO1GAh~Qcc+2pWKal3+e(#Anyl%9)yY+Qfa+=fXMaER6KoBIGKlWlQr#oqDqt-ud=16SE@nZFx&D=*TAyy>b%p{m?b5X&v(k%0aD z9^}oCG8>m5pw;f75H62gk0xF&Eol>{@Vh7Y7F6$3HaXRufXB)i@O6OQD(OCW7WmN&N^Ge-_frkUGoMWh7$08+hP&{!oIBAYlZb28+6y zycd}}k8-3M%Y>N+IaN0z0kdp_3nluEhBmuR?pa|Hxh{NwN<~kSvFwBeSq*#6;}y*x z%`dnt&j&4QvZh6rmiY|~t+G6%$5R(b+sUQ^Swk){vTOmeCF%9GFKeE9;8r%&xp-xf z$h5)|4}O&00qOztCjiLY%E)F}@22EEVAdTGv@wYxY{U-hO6c{Dym=mINSw-WO4ytE z#a57Dzy-5XdV71F82o1by1?QZcKKo_+o-7q()S|IKEwo{=tr5HKyTb8S@kY&?EULe6nZFmi76`hw`v%_aNG4fYQ;KDGyK&?#tfc88??l-if-~&ehv*Sy7nh z{9bt}e3oHVQ2W4`29og_RE(an@mMbbWH|IIhoF0UY8kJ$_4{w~d2cHb7Ni);OnN30 zue7H2u6qn{yoQ|aknvL2~k6|*GBlGEhAbrn`}KupM1jhGR06;l@O zsucFxNL(Ts69VCbh6 zvz*qKbwfk~wA&8d9D86d8(jCfH+V3XcpCE?CNONoQmIr@G0*}`#qX3xN3-griP!bF z!es+0IIuD7G`@^kH-uI=LEh70zXe;sAz&!4a}<8~9L*F{TjS8(dlW;}TDxneO3@v? ze#w$0V5b_}o$a%xt7UB9%)35ZvNGvzK5Tls?mMLoDR{h z(_iG((pyqmD$x@?v?FNz!@&K7GzISIJx^?r5h=DJDE`d@`W{jmarA(lb`POjoM3yw zk3X0vjsX*X4Lhln_?XF?*R9W<(To&mjB#u${<^3qf8)%th8!SB2Xs-i)JD{ZbDNDr zSLN^;4z>Go_p-JaWk(!iEf~M7XC=|h8af^7G`)TS8qw-0HyB=}fmRU@-;8g4s>{ek z620BR!V`L1>Uc?e5r&TRp<%Lz$)AO?L(bDw6HfPm-=Z5ITS&;z1O1#onFtX%X8 znEs!Z@tjvb>?%6`_;+aNy)$H1!1rGk-cWVivdgCA-5XPXI7|c(!#85B*_z*X#%()0 z(viuY>Ji#b#oDV+umpEn%yV_XF|UGyi1;Lar=^^?h&u@s*aHXWI%TC}t^+f+zEpVr z5aN4tl$(WR{xeXOr$R{5Q#NYFuQ0p$t$v#QT)Vv~zf4xMKot7T&Xp@Gjc8YP$P0HU zJBraZom!@MFU8b7MLWyUT448-Lbew}uL^HmmkQn^2IQN0J0g{4ZdMA8$1y=Y;>vGA zYk^#sGoh=hv#PlU^}`(4N8UE(Kow4r5!!Xa?38)VVn@cz0WU#zmN0;40v(b7_lUKkAH zuRjhNiw(6Cb@RH2oSbzF4Zhe0da((?J}2+A1Cf42zgi&80rQQ`6dpXl^7aJ#M!zgZ zENz4xC-es45;&ck@$F7L`4Jo+1!wwb;qkA~Sa>v>+LaEL8)g5p%JU2onIG+>1K; z2@g)j?@h>z7z`rxc=_k|2qMj&8$U^i202s){vny9@QOEBl9$(`+N`)>s6KwHm4R!) zAmY^znP3g;w`TFgIA9BSHB(D$~K2}=N|a&FKpP3*^Z7xuELmK)8MtK<0ZzXuE8 z6+W<%WTbfj>&qQDv3`iUPkxHq#?hR|>=_QP-fH{yp~Bj4aWrExWL&t$&=PPe*5IOO z2&&glrHFHWSiXMm%Rw$k*hZMF!*}A@4r~L6cJ|;`szI0%pER630>b@~&iHHMC=N|h zG_9auFyu2N0E|@oW~3@9f|#k|ilGF@19W;uJSV#S-i`d|6*eg6tU-Kul1ncs(JB2Z zS2J0LaFoog?R3>1tx<~wo{!G4gGcX&2LuJ6B+{`BJ?IfVks~jTd6V-2;Z8iF{8`|; z%cI9$J-z^s3SZ$W((0d##%&{1FHypve0sSlvi|E3qLKvWmOT?p6S?C}JTkrr`kv4^ zBo#<;oTqB|<;uw851U0C3F|WZS)}ZoqMQ0UDp-?N#&~bh7*HzRtM0Se_XGo=* zU7TB*ff$Khr4{?*|u| zN53z;U$+(ej%=Wg+xu0Mx9qbFY$J!vv~n{LkLBxWh*{~J_pzJ?XaTa*)6*dzSWl~* zJR16E%tnBb@XsbKAx=txaESOPyQTA!-weLgmmsqvxs(YsuHD5BbsGuQp zi-KnrR&Ym5Le5}IPOW};-+qtY4y-Okyro#hwT+x?z?LeGm>AsbUWrT00!pk}d7d-d zSTy%G+}!VFAyH8@d)q0lA7hD=!BgHGn9v{yZ4hXzF+WKDuC|?s%r7l>-km@e8y75; zRQ_&sI#Q_Ug6IG`osL3<*SQy`f26&mJo5L_b5CsNZ>_86HNT~P|vHos{d!IjN$4N_|R*Rf`X&pKM z_YtDuD!0Kz1pTYzi$Wdz`2{&6Y3zZiiQiAdoncy-|77aBc%!MGEF=2P5 z@47xKt+xH8%=6Emm*F1ZMsK4*mDEJZc;b#VhMd9$KYugc&KenU6P(ywjFZ87w|fc( zvoJuyPN~fJrGp68Af+K`5tX(xpRdu=uRL5Hpd_Ll^AL#wR$WIc)bge4Y3MKE;u*Qo zeMO!H{+})o_V&u3EHab)r0dZ)s3a0HoOF?fuD+<#Z-(rZu;f_!P@gJcx_A;oRy;Vnm8;SZ}_C8h4FRZi=Db+1k=`C_A@Vz#i+!v8a+TA zLR*y;#2Vkn%nC)4i+TYYxU*fJKMU6qkl{NYNT0&y>jPc>d(SJjQ^TR7hq<_VOqjpU zIli77`g8vZYe!c?2h7=T_gg#d%S|Hy{&*T>tIonHPH*nki3qzJ_SymA(b4yv-_61pr*1wKSrA0ZH1n~s1YGM0l!?e!}L75zM%x_L`evD_7t+q)@WIjHXQ;;lC(n*gEfu6B`TTqGftHu6_PDU5_fYXYjX&@t1| zrCF^;qL)XnVj!tOabhCl;U0uymfvKrdCkww$w`6|lLEyhH(=TzqqMXX=JBZ^!#8f0 z{qJF#T*1}uaAmO18er}##63nB66j3s^1X24A;l(ch-(<3BtibTS*ApaJJ{mpwg(dz z#Uxij8std?a;Mv%D=Ze+*;>AiAAB6XBYE#oMxatbkJzVy%zJOyRO#k$(N}MW1 zBE-!*5R6x)(x_fy4 zS$z%lV2SJ(f7K!kk8w5kX^8m;0=FT>11xKrNqH!j^Omjsb|^|T>jA{?Hx#plRephp zb0i+1=B0a_r($9rpomW^kW#uZ7<>oEZxQuE;EN-H7piG&KB-5=wgX+5ilkX{o&w~V zlZ8)+S8F~nFEBV4{s46%pvJh0H@O+X@HO@ZT)^agJ`-nFlLiIj-Ouf!BUlm_|8X0^ zzoYH~bJFs&Y)Y>Ed-0PkYB6Qs^TqDRua^Ay^^1?(zg+vX;e&u;ui_qatBM`*23u$L zc?D;COIWQnGU}DrszMB24Ruegkri)tym;b$Co6XzqJ6itZ#s*@JlR@$|G2dB45Q@I S2g+{p6xvRwZ}PuBe*Qnim*62-uplA0ySsY`7CZzC!QEXFNPytM-Q9hf?|X0N z{q?`9shXJ-Dd2K$pVQsDcdxzH>Km@2B!huUf(k(p#w%G#RS1G%fgm_BWJK^vjojus z_y+GN{z?rQ{PRM79|nFWc9PO^dSm;+$<@%o1TwX;wKidKGAQ2TPEXWH5f4pkO-DUpse_?cvf^m;*q;bI%3vd86TTE8GfSyOd|}K& zK#+=mn*Apdf&CQo4=q)Ys4NxypPh+|8{=P{AK_JbgZwXCe`j|69-MFBN^q}tbag4U zyO~h@`RfOwaa6c}5L_h3)2AQ5l|sf*i3p(t#tLfhA$4bRBIEC4@4Hibi12V)h2BSn zqyF=!dS!4mGw1K;y(uY&Yt)Yy&kh?!g`-@UbM#6Evtbb%f9WJYisE>^l=M$i-HI%| zDPeZ)A|FoY2 z9wd?BX|>NnEqR*nL(8|~4!Ogxlvz@d$p&}5it2jN1>*6;9_FiNUYrM!-wNe}t!69n zeY5z@eS9BW(uD~lBa>N0DzC$wMdpXJXY3!2-2d#(*AY&ZzAM|d5WKBA_+eJ#1Pu)hSbDtAo}tB;{Qazh5j*OlHPh{yZ?wCE7oiZ_gc}Rmw%_a@ zVJ^h?B$~d%`=FGifq%yMMX5xz`Rdv&(wtT4ciz31iIq$;=3a&41qIm)7xdTh@dPLL z5z`1Q9e%9j0&0J+2c8uwSwrn>&oos;gl2{ATHaEMKaF2*F;Dn4%6e~U5sip24s+;c zjbtp!!BUcw+q<~po|U%n&}uE{aZVYQv?q}LCU!ohZBb|Q%;V&it?jKJ3|n*9xt?dX z$;`Q%&y?M+h+TOnepOACM4-pnbN$v<9te`o#gW&z4JvK9$x-ZMC3>;1KC94r*1iY% zQ6{l+)&12dz0VtTj39=Eb8oOmV1y*I9#cRBqU-5k4Y~4 zAyaeu=IeI~r`rfF-;TP?hucWA$vnZ#@cpnQ1v?cB7;m`V7NI$fs~;klz!oLLmv?(qVAKTy1uQ z`&_?~%-iFvU4~1fy^19_E;;#PnDgdz*FRC7zo>x2|tF5j6HYfe6e3# z=vvS}rKdj6@*U2#OvV+t869d{Z0Zp)QhD&XyiIsfxV{&TVyoPnQO(V5P3FIT?!G4OzKvv_ox>ldS-Ed+%m(kw2)2WRjXQM?QUZ%BFJ4h| z;&PKj_sADa;=kP&>nr2$Q_c)12o-#penj!1<$_g6Sm^L4sqOORg0fB9e6H&1@u81D z4;;1@7X*QTB$J6D?+PC+7^!n*JK-EJR+ky=#ew`5;|!d3=CF{kD8=f2n_z{A7bsg!=RKI6c`A{s#_n{+$>Z$Zb5c)15d(Zrec&Lq|j2XlHRfr6gyo*YOPZz zZllC{|F;Is+v6O9ZXNGo|5kgf`7;j_C56e$@1w;e5MHlAqK+aP*c;h9;`3JA|WBoHaHfDPW7qj=rS@$V`x}iOSCd0dB#X&FMMEI8l8I%W@=Zu zf4niHm`3kYPVb(2P5V>tAOvIE&~s2}R!K2fDYqxpapSO~=}lZhLZrzvP3o%&7UTdw z5U9iDJ}s)ro)1S#3UVHsN8!q{qBb_HpLuPpI9kkCdM}UD5EzHeD1N+z@yq^Ob2t&i z_dK6|uHO78Z>THgow*cGY&YKmYk*Qgpe}2fN=w!UEcB9~o$wTXcV+?Ux~^xr??Tj` zc4=OmGJTRPQV}g;sai_NT-<%zL5ij+{$7F+*J zGd?LPgp|+r%YiRT;xgmI4V?GM*^!yk!jD~p0|+u3ucY_UZH4s+gb9ry>xeRc6CWQR z?j4HRH8DXjJw5G~FLmND*akXevw%&*Qtg~}*Zw&z|Bwh?H@x4XKvKXgM z9vGpc`J!O5;Mpwi>?L=#_~Yk11C(7`Bc)}g7irGR%S&(|?|3UGK_g3*@PjH|h@(zM zBOGbJ1w3N*=oY)hr93hQikRKrUprSvPc`RfNo`q_R%*;-{7&@>Sygp!Xlb8~GCR9H z{f5xo(p>J+C4JlI14qJV5eE4_;3k*pmyoE*(JwErEFEl^9y!=^c+-;1<>NazIFuWW zTdZynQV9v6v*glL*gnAA)_C%f@D^gZx?VU;PS4Gechp#}^p6-%UEgGFx+rJqPby_p ztksFwonG2Ya+|5Fer1XLLq_Qbt5@gkNg5^XpW|fdF73A*oOb>YJLJKdZ*9e)Cbo?_Rys!9 z)#kkpye1Mh;&0V0$L35$k5^2Ch5n3kE$*ow-$_!7bxbu_^3jltg^CvisWJ0h@O?^D zWA1VEzA;$Bip&|eINC}_S`f)sVPs#u64PpSdhrPrfkcKoK&(v0=#L^g$uO25lNt>P zZ~JoIz(|9HC)htEd96`PG$Ivse7bb-^w~7^4#qjZAtaSvqM+vCj#z7_6}awCQJ@ic z$L{&|EY@iDs+*BB=N;$t$>gaSt;$PO*o|D2S5--v$s&Vv6$BTGwNe!;!kK)=c>c=# z)B0sYL;X^B(VMU)2Yi2?I~XVwld|JlF>PsYEjTbV*5IK-BIRnnDfa7c> zChT;DBk?Y%DwkHH>}}|~$qj0}<;J{xIqBbwP+ z-J|)jADhFad$h$T3E%n?$Qpe}_i@%lrR#mnI^`u>b1b_?;oBRrLX*va0In ze8yJO*_o(Hz{g<=Z@sMTUi{{K?{%Axce;S{w{OK=DdR_LhV(}l7ok+hQ;p7+>Y90y zp3KfoQwh-$J50{^x9+=B6>yN<@#?GHnYtmPR_RnWcIuRKn zF1uQ=q5)KKB_%8{RHUS&jSg$5I5_m6{L0Oxg`sNDRRUu=*5qnSLqh_(m~8Gy%0PH*ESqbC0vUfb^N&FBXRNF-bhs$5=x_xb zR*|>PJhn%2kzlfu^*zGF_CvIHCQ6tX7(&CsDl03)!ek3ZCZ*)JT|Q<57*;#A+fF+bm(UN=u>)$=ej%XsPiSyxwg za6ss0w~FA#c&gkG-tXn*A{-5kfa^XPO!j9UOM)gAfsB}HZ5F*2k3=X=wdiYlI$1<` zI5qTyf39vjY>_6h*l<3sOKq_;M&f#H&_A9;cem-|+nvp{!(bAv! z?Pl-&%z9=uR?~mJ*tFf9lfqtIKFr`UhKP!K72L{3oH>q1;Yr73_KUvR_jwW$?xVwn|_%Wd9O#y+<2m9sa<#OK# zA4v3Ke~x8cuh|VpCYC~KG+&V=zgUeK;Lfpc>S%Y@XI++MEgIjPVf@n4Nb{AmV$4VL z4Z$N_E(T&9j>u3NZjTkk4XirKY8Y zJ32ao{CKVZ8gz%@@PpaOSIV$p)yYwT*yk#F`0>~RYmeEeVm=WR$d_@@btN)Jm5gT~TYjwB6r zk(z~1%kA-iq$Ib)s;r|-b4CU^3{|3>R5IJUr!-%{I*U_yN(+5eVeJ3)E85G;YcQD? z%;INWYdL`2;QhoUB*Mcsd!tF|k_T8=c@FT&$P}Q>ri0e1l-Bn@aA8F$C@8*8?#MfD zU6h-E)x*O23-w_xM*<1Ns_(rJ(-Tx!QO?w;+1XiHn)qSUEN^K^thTYenYyxu!94lo z&jp&*y60{y4=&89BW6Jz2IX!r?I4@5TmR*Db8{0d9{&QOgWqluf`x;FNzv9u!Xy)o zBwztO$QdzP+}VkoDA8GoWNY;U30)~uC>#t44Lduw_1|h3$j#H!_{T?jKxO$QCnrnk zDP;%*gA6FjocKjK>m|X7$LS_aszS)8Pfu>oCf{jHpMwi>z}hIUs|!_lDgkm)Z!Bf3 zda(u!6c89_f4cd)+I$odH5fKEHMQ_<)zjIzIeGPaQrl)2=jl z0C~VtJ(a^Rlant2_nEG=kI&2T?{5N zHWxbr2?p*iS7Ox)l*lV9DjpNl2#B#?Cj_jzvS3N9?(Zwf<$a`~p^+^f{|E|Um~1#C zOl+KZr2<+(K|xs2ii(Og<_jONopK_L)!VOZg6#2xl2X~W@#dyRgIeK?b z&ru{wFHw;E!@|%J;m$Tk0zn#n1#-w&)gne_=7<<_!4LOdR#wkzEGJ_C3IUsB_~UX0 z)z_LpjDpuU)@pQZn%w!@2{Z1yj+dwt$R!}vr791Dwee9pm#K0PZ2u_GkN2F%7Z(g- z*kIkJD7SawX_sguG&KqGSpWSL7IuBUCl8r{Jxfxu8cYtX=>7dYKJ$fsu1xGxXzc5& z1e%k-)#egVq3^>zxWu>*)6w$J%*@Q}H>*Ei!>>~CLo}jEco-QO!$Q!Bjb0~r4oAes zGSp}*Lj4mHN$_G+5Vyyv84V|b(~^&~t1BPd&cT5#W-UmKIM6qZ5|ZHH;MbsmpN`0> zvcjK-IXA%Qcdw^%o9M2X@GMwD_0K)l0el;rd~~u3_Q)9Y>U4*XTyOWkpln_07rzqa4OSnFv8K^+S>Y*lq3zH9en$2gnE%0yk883 zu$-i%c1bOFXrwT;%~N{SHJ9!kRYQeo&7Yn|z)-$y^t_SmvK(E>TM5Rn$c(&0Q&2@iP%*#s%%68tF z07$N^`4|;i1d-a4WNK^-2Z4xXt$gHPMsp+V%l8KxKWe z6lB213<~jsnM!@BQ-3?0BSD=gH&Si>CMY<#!;E|R@@PdW$<@V0T2>YrS_GR2fYWC@#>=;Qh!GzL%LrJRuipw#D-f799m z)STlpcaUiUe4UlM_1aKsqET&>FcUR-o^1{1zzH~Q`W0)IeF2$TR#vwCBfUI3fSxCu zcuy4oyytDZI63h&NCl(2IaNVhYc(BU+j@lv_SJycSS;`;aTytU`wj>APo4yM-<(rF zeTqowbD7iA3}7-EqynzP6)96Kf*_FiA*%S4`-9xjThlkQx1rwocH{yXn9w2E-gCqP z5ihTI$|@Yzv?G3oCcpQhl5+XmN8B+0)Y#68-Su zDH%EW(#{TA``DjU4r9a*Lz&UZ?|g{aS}&I+1}J&0h@ccW6Oe`hO@Pl@H;l&C3_<(= zw(5KC@pSj~(Na@CA_r*&1$1cfY9p)eGD@ic4u={*3qhA%z$QNffr|jy?a#hAsq8VI z_)ZKNOjnw)6QP5dW;8N324vtcNNlR(!YnKpAWutyK+>)@^9N<8)l6;4*v#)tDA5Dv zTZ5R>7XFvqj<3_|feu*bw7IOIjLv8yBL;sck-s*3Tg$p`Fs`i=4ri_uFtKn&{??Bu zs2==zCEZWrtsR`P0?9HbYR_IjW@#Eu?=DS#J&=K_s<>dXSNdY{ULP%d$Nytdv%0ru z&C12f`dg@sW^H~aQB$AkC@It*4(W69v)$27UvJmi+FE$O=9U&F5fQL1=RFUE9re7u zyb3`<)ibdMV*a0s0HnuM!DX2kvR9I7UzH=nK*6H{@263wuSgOd0+zzn^|flQbhIpM zG9Gk&G9)tKgPqE#4#bBEYO|rJz?UY3*@y7DjWC=t{o@uY*Q{oE6Hv?$>^7C`nuDx! za&}t-G4DsOOApUwX*rVF4D$H2cLrzQ-bO}7qGba3d3kv$N)z8cK90|#Q`-%e%1EV& zJd{822Vi|{0s_PgrO`cfbrO}_Ju2C0gg zlM~mXh6E5~kH{6Ivp`U<5xkx6=zxidi5cE<3X6*B2Jm$-Io!7GjyD8@6tPA-y1JU1 zpP&DU=QJS^QCJ`n)+6c~wy3dlb?qA~dQ)N05uiH$0-#;R&)j0#+9aSDNAmUc^}4^> z0Bg!PwKgL>O|<9EIWY}~|A8j%gwJ8rA;+v7-C+Zp>d2AG`Wdq)N2Yvnh$&!+PW(^FMgL~`Sz@)nAi-vyM@aUfMbRJsrs?%2 z3)lDVjPP%rZ7jeIgbgbh6*4SW(jRMXs_Fa$h_K(6+pCFr-s=%L*nWnUd5xi(N+wt1 zcRTtZsx8FV-51$~k z=M&JWGJQIJV#el;3a1N&68K4?@+GRyFs zqC7KX?}up+gb#<8^VBhWxP$Laf9AK280Sa=84O!c72ZN(7B$~0|3t>bAmSY5QF~fx zF;qt(h;D0avbAFTN|d5%%Jhl=0w-VIJTJ!>g>Sb%wwvi3ZPWt>ZK?F;ekWVz($$+qCvJb0Wo6` z>yQMde~vowM^5Fqzs)`T&z9pwhbZaQf&%dm#&D#jnggkwUA}4qM4ldM?A3lVTdBjF zPQyDGvO_-+UkIVOVZy;9d__h0RPabPu_PBU{r9V|uSyu*f9OIP>Qg6yzpcs?w zDh@f(&=h`>({277=2i0KkOYBWI`JDi4J{p~dwd#6lr$WC0~gh6wd&7N6%2rjlh0FW zpZSGQ2WVgeOE^ALncKS|`c1l0(X&aI@h(awhEIok*{{oxth}gYt)FQt_DCB-= zhSkj=ThyFGo3zOnrB28($z|_1aS+ubEaat#YkFCZUZje`Nn$WErv%@kfz*)$10U=t zRs4g#9gymPLRIw)$Q{@C8WXt zY9B-^s{ix?ING3!eXEw{AvRWqisx&JRGAHIi)(LdmA@bgUBi~PKt*~LS$Oa;a-K3L z8&Mv@P6DK|LN{XTPd@x&=IoW+ADOjab>x|I+nzv*D~!-vop1lMy-VeW71%88E2{0p zP28Kw3vzvmfHN~L9-h4Jht43B%e#vOO2d+Gy}38uQJrXq6eweEc#P|q?zv)E);2Is z5&3ZwpHZaRk!98PW50V$IGwC3-X8T);C9kqzL9y zoc$6nLuQ-M;DbkLOSIpT3%Owf;wA*}Wx9HLIK;%}_wQ`LUYx>iC>}g%54ge8-Dx~f zj^eWxS33!izq~tr#9m@~svTWja1tTtO!m4xKa;>JadmaAwq0oLZwAeYV+SE+_8Y1E zu`dOz9#*tQlpNVbIXo`D!5h-@B9wY3M1!CpsE7)I&LM0Ghr6h#X!B1V8mM^6t)@AN zqNUS?JpmgU`=Aw#WyDT2+~VoFJKsVI>Kg(YT3Y*^36{YOK|&lHoT(;Pw(ZHX2icu! zqaIY*Jk{>*ZYm($=k) z0ELE!nE@tpb!kbAIWYn>raV(?&E>le{xX)YNP2vFN|&el3RE5WfIfNq3Gc}hwcYFM z(=7?G*S)v3RR%;u!9X%w+6JhjqF`ZRAFB&1Yio1BXnsmfeQWh%!TW+7COajCC~^x# z#bePI5f#O)Ov+>N1OXB8V{|meix)3O!f-rO|12Kf5d z$@);1GBq{z>guW_YH+}3K3gS1R$WHb0zjr+FGsQ!>NUA=e;6c+bl0i1svxrY&JVck zT+>YVkjHY)&29LLRAg8fBMS>qAW0+vSb3~40b~1e>+IU-A}ur&Nim&2{t>pIA|q3Q z-VO5>3hT0Ciu*(ea{?%?rM#pDo zd4+|HC`R#ZAVI@s1S#14R9j{zj?aheUdiBZth7AR-lbCVPSf)Q}6 zN`Ujtm5L;Y2Z$7)q8~h`uAK%1OpEBC`5cW0AD zfD)*-Uy=S>Y4XVbjTD!)YVWqqOgjp6cYkxxljM1QYK{xR)~1n}aJ3LqPB-e$&0 zN&-s3f zI(Q9o104fHvX+K>R^e+iOHsuTw*8?^?-z|NgzMX<@i0a6bI zVg{>TquP@3!WSttU`x_%bSmm^2IP)d*<>!bX5E+_I3pm$9*Fkm9c>COXHkJv zNhIj<91vZ>0M{E2rf~3kUprMW++Uq+a2T{h3Ry470kt9S;=-HhzMhJd^zJis1!Cgp z2Se84jBT5gn;XB8krBn^uROUt4})wVUo=j+2j?EaBOoMd)havH|U2FuE9Nd$_m6Y!C-LOkb<_&EP>sdb$BONhG;@lhD$4 zK%<|h>9Qi>bQrKdLFo|>z6lH)&XXtPwwyo*^j@6dGZ-|;v^9WLV7OFIa4`J^o*&cT{sYPV>DFl6kN1z#gS=iT*k++oyF(DQ zJ*oxHLp%ciO5rEx(Ym!Dm_{e+$ z&JO4gup=WQc7UD%H)*{+mOlxfFMLgVQUVL^4X8O$&=KGW;P5=Ra@rn4kREY63wF!a+}!Got{bk^>SyATr>< zrUmR^rkImR4L)}Q=lgS!#9SY;!3BWCzu1ok6|ND8BP^DUno0ex&S10=ASy-?alnE) z8GHd5QL-xpV+3Si5ZrJeO{8#}mh`a9E5nDr5=~gS?E4 zg(V{=hYG^`aJOnG$&-{4O?@I)L+bkoj_>Xi$M)ZvXTaYWugh8hIO^dP4r4|hp7G{E zAS?8bjwS$F$NZ6bl&m)BfPLKTQx&Ao&(ELW;)*jUrBi)b7F-Sw10yH{wv(?K#Y{9b z0S=CiEbEpv+9Y5BH#9YYt(sm_Q!^qgj4H50R8mSX3{9|1{>!+9Imj?ifOG=@3gGC? zhO@<_S{qJJPI3uYwBy>^M3@rgEEZaY_1b*qj8eyoHQ!_iILqd!JlR@pYomP$(jx@c zP19zA@=Iz>%}JxSRAIM6DwymR&x@q(q)@=>s{m0(wn*(!*#$dGbXb_pLaQR!;vP9& z8yg#Lpyxzl-rQWwJS%G!2NS~G_OKEwg6wz21yVZ*?tGAb;#G_2fHDR0+$q?LQZ@)( z(+}ok08^O*I-QW5O!#BX;rQq%CsyQs$}2rQjMsKv0SLO)fFc6~k^9kp)4|8OIzG^s zZ-ARD(QT;g_NGS!ioq)?k4B32%>N)0oOvXN78Vv}1q_=-C=ypmU&UUU3Po{fi2Z9< zh;h?k)dc>ZZDt`2H(w!v*+2h{oA$p^Pu5NgLo7YCLxPRISMyi@+ zeEHpJXb`A^-+_vhsc&t}~MKRp$?kFiEV+_(J z5g^ekhAe@7j)?06I#AMbG>S*+?TsBH6^h4=Kwl1zj{E`km5w6v2hu*2U*H0;0wDm1 zD$I$$PPNClRx4**7C!*|^3o@V)6qW~xl#z7K5Lzc|sR*+}G)3;Ag6N8nc z)#}xte*Uajd-hKyEDdl^X2fM>#jXt`)AI1(A2gp}g5`9yRgeV>&HLO~-yU{hr2wV< zQTw~Qb}m$7W_*IGkj9Gv9^w&A01So-NU-Lc{iZzax(;AzR_VyCx2meCVR7*b0?^P9 zsMA{2W;o5w&A^AobK>}S;jvj@U%6aQvjr07(ROi}5y*Kd05JlnR_=4>F_^}O1u(BP zF#7>nCp<0=8%#srZS^m()V_cW4sbFrK$Kx1A1wj_LIUJy4##zc$B24lw1MFTU}|`= zc7Qny*ooY#*BPYnMZyl_t0u6GE-)ug%c+Yvxwt^ZpeNm7zy6~;0ifa8nQ8v-J`w0237s;a6Ph|5QK3Xm~l|7FdmL;zJp5r+}{ zm_>M#l^)Plt`*z zP|gdTSQ|LfDqlpZujkKwl^_lFC30x=$LFLZf9n||yWJ_2C{n&on!)ZM3Qjupr8RB) zuD316Kc4|G#hvv0Ei4umme=)GK^X9roHe#IH)Dfpo}!=F2yXqJDv}ruXSZJb!hj%d^FP>qF&W(f2$53wy8jUZVaUK52(qrUsHmUR z3$VGg1Mq}HPL9#;^k;!o`=Ydf8I+9KuXCmIv?u20DS(Ru5eRj#Alx5!bbtn#C*w3! zi=F^<)C-u6rSwRQVEd97f^DBg@=oQ z{9DpEw~O=bJ7$%7@x&j#g-^3vo^4}e+@IMEnaPE(T}{-(!@f`A_&(SvVv&B7D;P}; z#ZUP0{*e-B1pTF_qf-HCw|{;<6IiYuWmv#L$m&2aS+C4BiwVf z@Nh2!k}xPo`uqAq=e;lVI7y<{y;c6iY0x`&69SImE5L&UsrpHVKLK`rY!&FX8sDr9 zW3%MQ#jVbFhlVSRb-2w{8|6{k$KC?;fC3WD*Pn&B5QTUY2S@Q3bvKZ+afpZx4-pPt zPpkpHEK3fhWFT3Q{D;w&nrT$HOkPk@WMt7v1ADXFfD@^thlyG^$9YiU*I@_g5^b2U z$Y(lcdaY#d@?QdX-)O;~>uOjy%cO?!CxnEt+BNm>A~`fF<2^Ou>OaLtP?6l@_T0a&vo2VV?I` z6gQGY37>->IJ~7|#(%C1GlwSF3)mQPXdsFlFtz{S&rtul3|>4#;!)Q_=#}i#-oOAf z3|WIg#sdeD=Vz^*=AvpHiU`uevw72=WS7*w*PYf9>YDx>BNY?wZQ69Ts1DYVVax9VN?1VAdp zV#&`Ed0+#zsI?Czsb@|=;Hagf9!_hl+_wTW!|8#X~Y`ka4*5D}`|9RpPpZyu_ zp@=g;2j*my&qTe7Fe%Xek+(Hw^XI{ro2}f>_(lR!e(An*N zbP%;{=Wb)~P@EKh-S&5vX4;9I?(a#x2$Ys_Gi@16LQfQ%Jks*LAcCxtp?LVn1|Mbr{{??$se z4tiibf8(BKX27%to7B4HN3x%vNQzRp|5R$n19k9dEP_pXAy$_^?fv0Q)+} zK{l`bYz8qss>xGtibFg+Hgp<1ZN%7f+U@I~hjVU7K{sjl3;V=Hl7XK&rx+1dSMj2x zMQtI>TVvF$euFo{q+_2XN}j|y_4y6(p|3F~t1IMHS8IH~M5w4B1c$&PEI!!iSx+A` zV3P5xzE2$+u#0~%W}P~=Ax|f{T(bvoh|BFcUEg;>J#p*ooqvNxYG)Y z?j{|a$~I+aWUKRCSjErQcSu4yrYP#ionlEf;fBfQ>zTiWhaD#_M6=q#O!5aW{SB5c zrl;&x(GokyR548)pBv6*Yss7-^weZ|Dh7hG_NxkZUc(6a@H z>dW*jnWGH5;GNhPdBIAy7KSgTQOtDS!9bTQ^#s3NJ~|{1{EVfn8~mwY3r*}kYya$x zV1`4}_lJ)hM_n`K8iteTL69N%hUAIuS&dY&9YT!$*iy1Lf*Y@be`fqont{2pug4Kp zA{wE06#{Cm2}a{HF@@_iVhfD)8hfobL^HB{(%*t|o`5Xz4pxvpNc5J{7JFZJtpKx}VG}H)(0EWfiUT^%tS^_32 z2o;P=s6=Gqt1JR$g-vS#cMR!KQA8;2vvv2D8+jD`Dgm}o20cIj5Q1cTl==&PNA=L8 z9EWC+DPXT>T-sM_Jn+NJm^9SY%$A@Vg(^?1E-NMBDAnfMKH0N=UWU)*5Pi9qg7{-g z8auVsT*&8DXvI6zRKcrBa>n+{;OR@gA5epupDJRv{%9JT2&JSgtUH2T#rdv+TsltV-4zZ@$5UuSav8y4aJ z!GVeYn^r5om5Qp%w#KWCtAo)WeM7AMXuojACo*1(^P&xZJn+2S`FNc8|K=wD2_V2> zl7CKF|NjT10+ov-TJ}{#^zAKd*xx76d^pCStI1x@qOTxl}#OJ{qX?eL6 z&=7<%F__3$Wsx>>0^-bx8qQ~OYlEp;yECrH;o+~SXaH58BNdr%_UG&C)Sp+oRade~ zN*#b1eqF5L=YB2npew4c|8jYytYcwe^6$8*G7|`(;jPol^BHAGaVo! zkMmvCo|4i9rnUl+F8UIm9fd_%>E<+Y zy0C%v0A(N!o*9s_PnCV(fMH2iUS7tvGL%w0M-K+7EoGavmRmOHYX;i?Y=0GZSQ z?~`Bl1ZRyddyOy3zkKn@0Vydff*?ao+>#UfK z4TY7JTH!kpbfCfo$>miux^@ImLXf{djN4jbd|3E&;N_*|;mHZ0`t_jpUukuKsvyv9 zav`^S(B` zad5*SVCjq^7lf6|>-;H`DqLExTucT|mq6gK=It9r@crM<1%M%B(+>f*@Z#dg9{S-_ zu8Pa6jWaMFf#8@GE}pHy(K3F$&m6bP?t*VtC$PH$wmL_Zi8>xRPA;zyoFaf0|KvS` z6ku{k%mC4VuB;sEkm%W#E29iS7GsBEfbd-0-i9S4G<7(n7YqLN|J!m*&b><8{II#>`|hSQbf>l3pP_-){!_!$%B{&n-G!R;`0v6MI* z9v_Roe{W3o(u>S;(x&sE%~obKAIEI66yM5v9P;~>_5eX(#K^#u05?4HM9EAPh>yjF zjq8*FK+(W};77|)LV^G_)&T~p-frm~(ECZi0K!6G!ZizLJ;4mXLWOS_%KMwMv$gBJ zu22C-Ys@h>GlQ4#G+GA4=F0~!n9_I3-P2VM+4?OiC!8RR>o!Ll7iz5qbH=x^Nk{?= zyAWwPju2Zfji`Y+G{j{uGdN*%t~`x%O%V#3t`d;|)g0hVKx{^Sdh(_v1k0DAwe=xcd^6vgV#f~-rwhN-T^vUdFFx+q1$RuW6c(F0-Ipn?H(3hjeoHI!ZL4fr;_%Nfc zrdFe!DV+-gMG@m+B+3N?1u>_!6=r%GoUX5^;3zD!wQHE2V0^-&V`zB{(=41_}>90@NfCKRfE(b#U(2ujt9F_&jJV?bBZ{u&^Y=#HcMSEMWM% zZo0#t->y(n0`n_k-Mj#vpY_Cr2+|qY%WP| z5)2^hM9z#(fk}`{mJ*3geRu*1A{X;$+Z<+w}EWTJ|I< z;3g}~-)v)vAx4oB&RCH)&B46-Xuz3je{RJJQ3=gXcOD%(xP;5g{q)hQJ7b+`_lEqVSz=?`-oBif{QCVn8BnSPtat^V6QF_j za=3i2vgG%=R9W44*Bye9R^fT^5=gkW*OSUMQwQlUxa@B)U%fZo=r@&wF<9<~2V$B2 z$)E72E=}aXf^B+vb=5gDBaitb4!gGU8aE^f4RFWUgnR@D2*Jm1Ys$-220uHa0VWQ< zeOeosUu6~Nf+Hg2WItlOejUF@BBNTQ28>NKwKj9%9LCbr{QR=|6R8emz#}1!PDT`b zdMd%l#1u>iv$SNG!p{NoCSSpKi3URrCnyd(@o?>+U>xDZtP7eti`VSW=Cq?b#`_&UXLTjI69;ii#{COxc~c z>7qz@nk5c0y7S|-1sgpo{w&i-O4gQ1iMKa6HfS{*8%s({1N-qqaY3~nM$8eG(-P)Ry;7p!nu{(pXzhztwr1jo*D*ufQy|KFDmL(KU$jVCwGUzIAP$m#{= zjH7&{%V{me{P2d>ikGyjm=L%M0Jl55Ewr?^vf|+OmW6 zbSM%S=bR(Ehub@J7Q(kB8e>ElD#bj9XBVN;D;>5g`m!JCv`PrLC-_9)y@hG>Yu`~a zocnB%3qw#vtUv$q@c0}svHLZB5p=K0r>)9UezB9B9u8Y9QDSdSc-tuqAYg{V@kqlt zW$GVdp$CPA)=p9cd1r{{_=ScN-23|vyL;sHnwdFPc0MEimhN~F8Wh|$(}zS7ov%sx z4A{;|q7mm`zC=brLpoeBU1UWs!Td;vf^l}HQk>C7v@O`|?Uk)zIjr(-p)|)2Jd|7_ z?mdiui$GziRE|o!2o*MXVO%DDMRE95b$R$W(qnqIfS~d+t)GTD!J7%;r6B8ioar z@^8M^#p5z)~Pk^>BdWq6d(??54lTgpRuLrm9S_AOKlf3B(L=hKgDU$WN2sCm+wk0n^6g35@^I-d9Ie^=c?u4Kc^S9AZqES|Q zHM(d`g&XjqR7;nAp1=MmmvTiF5E_+xAm)=*EqdXL&m;>@h9R)rfh7l#k;3J7ReSXq5yd>qcq%* ztidAFpP%VO;7a=_Y-{K)V`1r^Tt7hB`{kfS;~O0Of|KkEc-WQ4YTe4UJJ|15vBKhP zpS9C*rcy6){GiirQ7X$EDXRtkwd_uy`q|Jz!2&a|vQzev&Uy9c^i-rVXNh2Paf)&I z_^rp`Uv`&z%3q5Q4GcUIOi+-0{MfuOr=&y$B55CtM_#2n=pm;MSssV0Xlt{eg@P%f zlVp`$tK?DD{70eK+NjV_+FW2$t2XELFOG+=Q)#K+Bg0|Sd#S4KujUpK*4iqwoLfE> z4(g5e8GTk(R`^Ec616!vOdVp>Eqb^XY&Nd3=@pF!f9lvjXia+k`{oB~5puaGR`bHO zP0tGS$v?hDiIP9TlIA7*)uQa@tlbQ#@6A4VqAp$8AHG^nkPe3p=a;f0YaY_L z6G5qx%d8*iRs#l}0mz$OQJzrU4)-6+q&xl{RPLsNP&~S`PbtZN&wuFpZ|C|GD$$cQ z?zOSf>=ynzh%f2sX*D&JVrR>W`I`Q)vcb0q6JlX_Wn~4n8ZW4$Bw2k3#j4t*cDPr{ z%ku8P@-*ZjUrY*?NeeY4(7pksf#6D;zc}B#K-I?7ob8WyMd48klR=%EMzs?gj1|h5 z#G^fx2-n!!Tjh;fJDx|K`UTeGd^j|sHG_^Q0oB*nR}EubHiAiBLQ5+ad>EiC8DmAjgmpWjAvzJ>LHMvQuDY6?|K z3w1)+m0u&n!n{FLehz&A3dYw_y%nIw&2CuNnU`frt1a{nnHK+*fF^HPjPqNwOA0&I$)g3n-g+*{nSPK$;IWGo#t>eke1Q_9VNoFl8K{^>=W2 ze1xz-Cn`z}{#WnxbkiUQ>F`gVe7z7$KT}e%NM>rHI2af(E>4%k49my4z`TmOry7ON zFC&)lQ95>LWaMRATU$UsB;fRt2Ao*zkmro^PMq$Yt5j7+NyQa{uru8;wQxp=WGYe7 zl{%R3^5IoF!`Y{qD#MgNHh$UoC4(}s zD|VN;YrTb%6q<0SFOpyvB&53 zV^+27uexlQ92~Yh1H;>$*SnK#GxB|+qakd;CK`&`>(v5UkNp$#t|7fzk1<`$9IeaB zc&Mo%Pk5$mU9;ip{y@HzfLe=9TT3LxT){e50V}TS{c%-QVjiJm+HsZOh?Sa|qu`N7 z1@(oOxWF2&3dMEzqLy2a3A2JR;m1R)OI5i#Q*I*S_ezRkiHP!N}_cqd8kU+kQ%!RtKAcoC0KnG8oLAoimH;7Tuz-(0zO+tML5Quz3BVWYWAtlEi*8-LS~cHY^@JUMM%WO@DePSuHzd zyZ3ur4-Iop2}#jd=;%HZL$|Ed>c-{PF!JK-)Rva7 z;^jtp4+inNKhr(RPeTpK=3B9uy1)YV>*#WF+jJ z?e_y9b^g(QKPVJ`34@iHB^#eggb*s@rh*<>+`B!b|6KPMJWlaZ;F0U&F(+E)X( z1e@#ZgRoLM$;OK%1+>caM;zX0;o6#Jk^FPAo13yxu>FXz1+H9__VkjyqLVPB&w>KIu?FkIDyL9Pr zHcIej4Lg@IbaIw#2(RVP?5HVC zwTb&?#XO94!A%6Gxe~pwh@+hVE@7P!^NZ9D1(Ny`gC=IZS54FaefArJ` zg4N>r+Q_ewGPdit6<(x#`$j?%bos#p@6X((B!q;|$4}!U!oSV#k1wTFRbeK1RAB;q zM2(hAu*1hlpqPlv&D{)n)EI0#yft{-Zdr){MO~Qpxqu;8H)9D0_zVdL z2c;m6FPn980gR6yDJ0U>fBR*ILin%%Z78PaZsD&Z7HgdZ$u{>Ggf=m?!U9p9p&9m}C|UVYD6 zwDGcaDP3KWV0X>3)zIwE0wP-S_GCv>hmEe+# zR)157LgG&u*$zP!)nzE-_34Zb+MW%_wuj+B7_1Ia0kMP`*X7^aYcMb{@WaRV9&ei(-;`Z zA|~T;E)B!e0AL<&#UX&A@w5ATJk^sQMD&##xSP zq|f1jrl#NNMU#K2NZrm{P)}38@@*^?d;7aEGr-y!f`K^SFL!>brRAUYkUQ+N0Pf;q zx7cqini?z&EE(PtV}0Av!H&-SoTXlqPMa!od|-)k)L1$?SqR9<`z+k^O*qNy|MWfs zL!I#1-Zc~xkl=hCTvKz(Cl&w{1T7O2IV2SX4lg-S)9+{E{ONJw70YJKgAD!yVk=6yPHLcz75${)R)nYG+r_o*o+(^6dP4aC)LEQDfr+&bTh% znt*A!9Mtj_2qfr?qzTnJZAs^Qe0}|R^molI0`^Chv$FQCT)k>jI_`PAr-=yFu9Amq zmXLV+O!ezS4GpvO)dol=l!LB_2PE?4{K|L+EEcLI6Pq)Z(euKZKSSwKw)dwZ@5&rz=&|!t`9(|zlOd=1i8g;wu6&|hx zjI$IOlwS$ze3M9NNPd738s~-vBXx%(zX3ifs%Bz%7z@BH#nIC7G3LN9|BvzWooGDh zT4M<0rk9>A0)bi-aqDS!b6P*#^n=DQhV!k~U|wSlk1#b@_%WN2ogH1CAYfl}Ivq?= z{ve+}IWMoSX%pMdLob^BzGpqqq*rip(Gfs*A;b-5Zz05iQC3p!Us%V%WT3>w((^dF zJ}@-Yo?pN9z2D}DVUt~+Z6aH;bCOJp-As@Sjh6pFBH(x>S7aEu5>N~EPD*=_ik}?N zH2+GwZMUL=3uV$*fy&l@scHHByTroI%aR6c z)?iM~>SNhh|C`+3=7RaB-nPGx(bGeNrHmQ5(#8Sy@o#)5zwxd)BoctrnI1qxXw*K{ z&dyF)baaT@hUWV)hJezMp22#bZ$S*!Wl_0ZtCdqr{VKn^_(RMIWKutv16WOg%}p)4 zbn>=ltin^>Bj^5`KpsJiOwq5m`}FD4YXA{~-(*t`Q zk>|acFx^fVrng|zZdTQvgz&T0P~9XLC1U?F@OuYkYq^{$Czrpk8%&JI&aOUqS! z3JRZ!3NU?*=K_f#rjXDfBO$>G?*IlJs0spz-c-O;Uq%3%`Ms@;;{3du`=_z-qF7D+ zzMpKpNB5Aani{T2SJh=Sv~yTa0P@7^JqVy#iT9{gYmR0=a5KRD8kTB7d)3%dPFoe` z$ZNlTF#s*&*W7&QWDO9?@6Vqwn`+ra$O&)BZ8UU5(xCP>sJeMwY|L${(De55OYKRU zi3$xsnxw|Y#4bmR{Irh=*PMi4;|UAJ$AXZju6M)@94(ldZ|O4-6B53G4LWa$9@zNJ zCIim7vjZ{nvM1TdhDm3RjouSnP*Of~rG+NAf0HiHVF6eBRks5VJcz67P7-rA zGo4ymXF>xAG}Nu_R}L_2vH{K(Bd;<6vLrxhZ{Zd{&HVH=mh9L$6w zj{W^HVV=?5xq|?kKOobl{OUC=ScIspz^L!b_SV6Hk5Z;@em(~w;T-D4>Q^G8X9pZ@ z6P3kW4hx)s-L&e&ky@q{lTe#FJEU!jj`&F3B?Y82)00QQC9neK7g%$`0?IZum3}c# zO}SQ97Fs-Ia2a=8`WZ9N3Qa3WP$~r-T_Z58SFYMS)yAW2U&AtWx~ z)rO$zKA9IUlh&%NcyEuj29P0VGzucd3Qy`)0xd5GgoYQDwOsFq;hwqP|H{ZtFGDkEY)tU* zFB(d4EV?sXa0Z@ zl8IPd0T%`?vJV2D=MswQKCV|JvxENHG*2Dei_NVBJFWQUflUyaa+zo2;ftM6+Zc3k z=msj3C`a$iIC{NFl_ZGR^K4bD&|)ywgq!~9bmMA?j*9BnwkS5C&}en9<6~FUc|wMI z*sl27X_S{a@8|H;vv9`tT^0Gn_&fC;M-GL;+7d=-S3w&j<6C3;nUusj&Hi%d1>gR< zkQXFGuyl`+K@Feo-U$6HpuoQLGv=j6y~mBLY)crenb}$Iz(7twBrx}+j!0rACa_29 z-1#BDUKkZie-abe9&rQKe1Q82t^ur0AYGCw&bX5)C?q>Ct7TtjQTJfge}clR!xW(r zFL9kG?6srU%k}H8pi+9L>RLH;Y(#fLr?jT#eVF9K6=iWCy?(-f&RDW%0R;U z*#|aj=m?qK_08`tGxLLI&$tjvDm*A*X}0Mhm*;tI@A8;4H#ZU&_7YUW2_OuL3iVuO z&0ohx3{|n9GEbPIw6qO0*d#y0XmFuV)LUN5b00VmWGL7L?AoZxXTQZ^Vjv(5;=Fh7 zV@j4R9;Ekxs36%&>-r5<7bS;((t03A_OAk7VI3ETtd|C+nwb;B-n$!`C|nU4sk*h* z3)O2^`qLF{hR`j-BO@^oWc=*72m?Ot0+TK+AX{+Ac@QXQl9EElNuG^LX$%eNU=+eB zHBjJKsgDN|JV@%l!zN?5Wq^rFEI^lPKY90lB?KkAF-6=2Qm75`^{_B|9Jrnbuu(t^ zI6l}6xSi;7ZAa(#-@O2rAosm>>+8;|P5?TvrP4x8nEm-1>ei1`lcV}~ulgEkv2X3| zU7CsKIDtS3s1|nO(&Gx&h<2RZAq+tEIa-nQA@=4x7Ak6LjpO4Kf(axb(ar+d>C?Ut zPrnGbK&Qs#DnKdtjjVBJ%w@*MQ1plknH@OIpE47ppqHYUE<;a<WDQHMMv`x_>^siwvWGA@TW zzW)9*c%FxBR@>uK%vU)Xkkt~=O;SrxuT2i9p(KGBiprIM_GJ$}>zf6M< zV*bK!J+p26puWC)5FNE4$qY^BPvmL8kFs<9-AwY6$TuQpY z0!6rM$Qoj>qbJB_U8B+>A_*Zo%&_u6ko;*)SXy?D0Hy<4p0v!Va9m>#?b9T@jAsl|lW~k`f$H#pmiyHe^&(7`>ui zMmnqX$H-Mr;s(sO6^&8ck@p#s=YGvsY9=e&v4HhNC)b?)7yzjjtB`Z|(b(W_??Y*1 zoBsJu)*A^r{_-P7b(jY?Zhvf3Rup*;G{b31j?PgdEmK6MCTdOqA^DFweKA&C>zA?A zVG1;X^o-z;`kSzHZ1U5=Kmb}20@}igT>?zAvNE=6GrzQ?Gi_iuWV`o=09exk4OUcy zfZRIJ-@ldSx4PIhNT7T#EhW1v2R+K?Z!qA55)_sDKeQ~Dq27*F(?JNaG@{bajTPK# z0d086O^T)Sl$GW3mydaTQmXzsxr1J)dGiCJnGLZ-qo+(NoAR{eWcx9S*8r3Xq_6DW zDyjF}TsV8?>d%;d6l+uPde;d;Y;6}3>>|E9I~_EM_#y~6dDsBi>&~38cXm1EHbI{r zJx1*$px=L!kMlh{!FxVkhcQyVDPHV2H*Z__`(|B6#yJ`Ph6$yR6Cnz>TUp^iU^D`> zjfnNE6BV7>=}+O%`4PP;=Z+1V1XKW{lAyP5B>=HNtiT4B#%JDD5}ccTbz-SWXI59i zfa8058uX*97LlM3)aZlNL26MYr_`qcTpBd##4;(q3@jDcZ!Gwt4=eh?Ti@u;tOdYa0aq5uQ%e2@Y>RZ_b4*g>??6np|l ztn;PNOoRe@R%B#kd~c5!g(?{ufHUt?Qwcvm0qOP#y7Z!KyH8nZJ?V5}8HfZD|Y?zKG0k$eihe1@pGux z%8J&L_|Lt;yw)^$c-K9SI0yV*H!9!o;LY=<9 z%|WM-4aP0u`ehdPNtTvg{N3k9i=Q8n4Re`-2P&T)8Ni{};n1yp{_A&!obP=d!@v2Z zS=rfzt0tpojup>ltAo^0qqb8-(d{EmR%=3p<7cAW#)hX$Z`A#WohW}6K0iad@o{N% zYc*sw)bW@9^mz_4;0Zp`$;#aEtq{g)q?>zz@bWQR2Cv(b!*$c{yNR1|UybaZB{fle z6Ef7TYPa@A{^Itq#w0d( zyzO`}z=JS#Nv_K-sQ~2wH~vy+h8t(e#D{*i8;)#$xeFo?gkg4nyQ2R>2>1v8!T;;1 zLzm~N6dxvS9;!w{e{D+2OOdjJO}tfwoFz8m;NBu-Cu)E^1I${R%PUX$+y6mE2sJ>6 z14@Os0QH~mfCc~6#9PCKdLI_jB)@?o(#CcfVOOA z2$`6+8vWfh9tN}qCeEJ_=r@xSVDU$Rsqc{W1xU9k;ofmVLtn zfpYb5#>9$cAc3$$h`%}Qn$*kBgBbQ6PxO!o{w&bvSAf=pctu1^eDG?})h|B&B}xi8 z+0-9mEr8tBiZAZ8SSGCzfMdk)W+#e;eDZ`xSeU0kBL_v7fR_y6H8-aOl7>Xe(;q30 zhg;9Sd|mg0sI|5A^k-c32k5enZdYc|*SDVv&MXi}z_Z$t7IH2z)dSkJ{hk>Uu#nGz zZ+p>B|L2t!!mDXk960qyieeuC;-ZjlOAsmjy?f{g;3En>&X`b?D3H4lu%bBXv7t4s zS+SmaM@NTumx<)X`Po+UjS!Fk!R<#RI2qDokdv9&2nIK3%yi$ZR1m~{bZpXYu;qeZ z2a18^A?5~vot5$NXE4Z}pL>jy+$Cm^!HJlD2`4d9^I8aeDxj=)>M|d63widYk;T9_<^7f2`kF}wX-7& z7l2|a!Cs~S0 zmO=5T{o2UmH`^`4J4KGGe9$!^0S@$5%53(-zU*?c9tj=O5MH_3&a@2kWRUm+-v#zN zP=ZW*OH}h&Uc0)YBLMV0)zE;oC8+cgM;p^ztjGh zAQ<{dpg~i^KW3)lBt%5tZOSna-AO$d2)H4Aiw;7}+`B}ekYj&}R7Ksqni_NzThkVj zbZJI7;vmHHjTeXoz>+{^C)C0d06_=T!BcJRAVpD4ADDkYb4g?met%wAW;qRlA*A^P zq@?}NN7qKnd9ZVsfVG8b&Ejd|P5?GUaxqcrharg#pjd#}@Cs;Lkb_9*VIrnIs)-Tc z`tczXe02qgWW)6_+wHT1fLQk%ioi7i90Os{dOuhQ@B-vlSSa;qxW3K@Cbo+DFu2!d z8@!uW^zWxC76*d9G*unN38fxmlZuxMpLrjL0CzV zovzz0OS8+FK7$rMGHEhTpVIERVgP9h&P!UI3OfSG0f0o&WiL;Ie#4g$fN1c5n`;B~ zS6~k+7hvV>ZT|Vg?Fe9{#o^tcF4iaan{Tfbb99WaB4r>o0kL8x#+2GeZS~iNcxF}~ z10!RFGz&IDAxRV)!KPIYAqXg-4EO~rYe~~)IX~=LX26Jmm&&Wr02=&^{u$<=1hn_( zmEgpimpGt_EU5K>owj595o^nvq+4mDidSCesYuJK|=L+krp_m1eQ^fy3=5v7Z8={Z>PN@ylT~-cncX z)uIDO*g>Jw`jc25YVGn3DbPtElg5Q6M1g(*0hqXLA=72tCQqzM9>GH=sHXJ$lXJRE zd>9{F=ZQJ4NI}9%$F?qn;)*#qXbvWdiHurL4drKFLz_OE3OW{}gS5bZ~5-Dp4v?Hw@-*T?A1bP$?f=^2`}LwHTd*2cnF zD2gEOC@DLG`ZhmOYRLh^iLw}{b_GfJ`1m~4)ErFpa~A>H5{L%~br!?NwFIDnGbvDp z0Nu(Uc0mt5SK!HU#MArRryF1zK(^@*oMWQ_(2j}$`w;f=44$BIxEnmD;NW{SSE?QNB)-rhf)ZDE`XQ{+#;+S zGb^5UjEmCJQk2*PSv#nP2B4hYx%2cq%~l>T9S|;0fnQr$A$M^xTi`AR$8aq$ii_+k zu+BI^cS7l{2z(-$80H0FYe7ReXrOhA9fAnm&?M<;q)q@L z1Hj7#HzFB!mqGzgJdhODvxdPWUuf85(%jO*V`<%$IO%{tWl{PZxW5{O>~t2|DrnsO z_Vz?4W z8fa{H33k2_D<>2i0$wnd%JJR-KW4B~1JevV<%0O?qgoVi1q29qxUs>e0DVF#j(&52 zVekQ9on)oVt%ZmXaLA)9tf_^+F4@|0`1+RQpM(R&S@&kHqbI+3v8;90?#IQNlj$K7 zJgZ{LEt;N@kqlh35cDbzroO_GzC1(O@Af;U4J_7DI22Ad2ye8tFWzsXBnE#f5H`M0 zJpfj0mp>_Pi`=Ew?>JK6L^hYhP{e=Nw_1Fdv8vV2=-bj6}7*c3(LOC z8v;tsYW@XrVxk}jfUrR!5KQv2cG?DL4S+H^9Tx0R;=XLh7zzIq4y}^pp7MfNz-f6X z-vD@Gk}E2RkjU$B2@ilBf-X&i>n9c~$MzPnt49A7xs7s*{}bPhddYU)J`FI@mxKb^ z>;`|OG>n9VoyVD%=+Qp~-ppxT)4*d*9IK1L2z2G1YMsYi?UQ#oiNhpX6%yl`L`q@a z-5#LQ!{Jyd7U=Y$sM(RGQro;&TKilGK-GMAyWo@eLk0#JyG*0suL%MCDLnVO+O}(T zh-`EGQDxR->p4_7s`$h>ICYZ zm$6GTxh+gFw6^SGmpeLNgU5C6QF~l^ChA20WD3;)O0^ZsZmL_`*S9R?7^k21{FC=L zh@0A?>{wwN<)(EXuTHZ+k7O8zQ~1#2I%4U1zm@Sb{bo&S!OZIPZ%|=_fz$<^1&7Uf zhS5uE(kErnB;F<_!;%YCJOHRL(6{OTQcLhJ$_xJYrT?P#;(rI{e+TEE>v8_S&j|~4 z{+w)EskC;SxGvns;S-waB+%e+z=BxVQR~pf&1ty~9#1qh+gmic#;G#=-J;Y{+Fc7w zu{mlK@I@|fa_RU)B3I3E{QTXY)?c$5hR#?+gYX-i!BUYjPx=*2s%yDb$F0=YbC^IH zbVLS==w7E^L4#LiA}X_2ei`yB=>}$;yt-3j#pk9znAT+Tr3(Mq!SYixN2KQWj}>7n z5>k@^!{&c4T8ZXhw+;eFr=(qt7pN18T^03dj(w=1e`X<=KIRHO>fZ| zzNyh(m6WOt7?}sfzm-S5vNY3Wu3^3Kbaa$=-iQr!eP@H6%b{G?3f-+k zJ*UceiM7kWI1O*S%ZUCH)q8wz(Q-g#*r4N6`;H|vF>YCui@vENH9$8>sD*)vgFqbW zI`W0-O3|-m*j&x!;!5LUGQp{nJ<{@6&;FFpWne(`dz!CYC`(+bRw{b3_2n&~;c4Dl zmYk0D8H=rNIqFDB!6zZKE)ih_1?qrA{BN~e@5ieH^z^!y2;mwlUN!AS8sih_cCDvC z&rZ&lYJ^T!iZ9JiVv55dkQ3-CN8>c#{t(jERmg=3-4l78-PG`4ECmipDVsKw+Wpt= z-shX^MuJsH=~qOluCdYB zDw^?3I#ti{f+Be6vWu>gT9?s;vF&|cCqk}69!!q<=5G^3fu>Z;uXhG9Fvy?!>xF-p z30M<#pYg|lryEQJ=?a}BB+qD;93tA=#v{;RHhlb#+3>Hbn*PozE*G8H@45QOje{&w zRmmhZ5LY2m0xjNxp`>_t_GmT^(!{xqYEmN*(yjBKNV$I;m$dARTK_Ek8l6c|;g$Ib z2G+d-<4z~|Bd8f-%&9UDvTx-?SM~C@2cUj<#aT*A)n3K|()^#Q=KjShtvH+O16zcI z)|ei&5H7ERf;e;pJb41Dde+vsQ6jywdQhP2RlkMbOP+&3K!NWLEQzUui>|dmei9Ku z(G9S06VTxfaDanp=?mC|0|VElZyDcsHq6Y%?%-)wdh3=k8|q;q)m1Oc@A;F2RmcVf zRoPg?vZw;1fud=^!r?`ep7$(M1uP}2A1&~oarCmKAfZTwiIv1u<1SJ6utNL~C}eVR znF;Yc)pb_!33TT37UcByg>H}jp-AnM4OPD^P%a-n7$Llxo9$8ckj@*4sFSiZUq)hn z`Sz{dFfumQe`4ZvV@!kqNjOOYjf$%m8f2ePf(OU{M=m>k>i$a6#%wHacR&#?1>Yst zTIlrNhB7|rC4#jQjt-5j!bXHfJEKA8PmQ`7Q|dWcspo&f9|qQ*Y}A7b3#umY$#5|L zAY0Yi3i7DuJD@fh8a4{=k2G!Mg?|XaXCTy$E-8Uh_U#cPJWzb;*rEo9hR_huJ)F~d zBgfa&skRJG6iZ#Xz78=0(QNjl;ta^`P@T z4d89~0#R`zkDLIBZbK7@ckNoUQHTgUG^oS`WBu-3OT*H!t-XF0)PIB0vDnfqklpBb zd9R0v>~lg~$jg+g3V`aeFs!i~H`)N3;@-c%ggThh&VUFpb6YGXsC>#54{-Z^VS^HQ z2G7UGQV&$%VWM-3Z8P8`06N(lJT)04k~{c7&S#2eJT@>v5R5ulB?e4G-> z&;VXgC{$(!qW*`G*1LD}q0!MCYo*#GK`~fv0TpLoO0_$j1SktMe%j<^G%Jfg*;hKYsCK7QgWQErQIsn$&ms&5sBola#{ zIW}3?mDEBfAW|`}UMR`X*(A9YL2~llfq^hy&!^F4FX5yCq^&W72Vo|}t)tZ=j>2dcGHW7G3|=_bV2 zDiy^lF2`|JJkT{9?0B!29kd*kFf(@;-is+%=~rAF1_R;wS-xj`=XmF=op^q}#89R( z)KIw{A-@OtjZ_(9!vQ$1wI|G*m`o8@Y2Fl&KMXN}ECjrA_`n(g(Rm3$L>&3us;olC z187IEcRe{D{?+h}Lcd%deS~Ky5{xgf;5(zIZ%uer3xPKU*aM2Y`folGhlpUpbCs#a zos)A)h=B2mjEod;UAW>=wq|pBk`}^w7Im|C75Vv|J-VI;Qgx!%s!}HkGn8@1 zyA+e z93H8$XCUFweA^>-LWE7i-h2)ZujU~q5VHY(PBN^{Q|#OHJ&XWc>bIb?gnb%FWkpnM zo?~sDcN{!*Da3E2YaA)C5!tA|K+AcvsY`af+fna)aPrvf>eSYEnX3s{O^7*d%bK*1^X$JG( zKTRA*D~+(~&R67Ww&JE~d026vov|4kCD_R>BYq~j2MK%Ssb$9{ByaBx7{U`JpJv8W zc|+_0dnHsgr-U+Ih2t~w@~n}&B6c&JMa3@>2q*|_SZJTm*YfecRV8|Kr7%&&I!ERr z1Fhohqx9ig@nB$ZNZYJrK;gdMMdQ2qHbb5jC`5riEG&I}PIG=2uL)o@8h(VO;S>DI z*VM%H*>x>@UEbBUKVW?b-hM8#UU4Y!Vbv|eVVQO#1;qm_P!#oNm>0-}h{MHv>dS~1 z#tGj&>yF9cX}Yb@?Sad2jOVmozaD`Yf#T{lz9nPI)uFtWlI8yG1!YAv#J)#4mXPzB z8G)8OTH2uN^NpIMkH5_-#KY4AbIpS*T>D17VbYR%svTABZq7NEgKqO-~@P?QyRJVt))8g_eU&+XCYjWn6GYJv%r z<`!Sl<^`9=x|NEmUTJuqL+MX=1luR|9HZ$rx0vv#Rtn3Z@YvXE;uTS`1zdqtBA6-I1)8(dkQhmhtc{3k(v93a^`;n@uYh$Ag=k7^Q zrwcFwj7AHFZw6z!y3L-SO>y0%b*}7?5fCA=xa~ZD^P#u6z>5z|+z{?kfROj7cy(1> z+9kf@1rGs^zog}Cs`SFb@14dC-NQGc@8x8l?=4|9Sn(wEj65UUbn*3RMBOx zL1Sb1fZg-5+@DEeG@F~{&|nEGx#9(HwKHGuz#cJ7-rpMuLPQUN9oC2{l*hN4 z6x*M*S$EQo_1_x!nECCxw_=%oUzlPuD{c%p?)k zk!y3}RHEhQgM}MvRrpl9oT{y85s{;*tw4*PoLLz5JK}Z>kNLgJetrlQHC2LZm;z2~ zWDny$hR2wjW;S_G+N4h`&Q%9ZJN%Bz*E}xr!ARO@+dMETA0MqeX)^S_JX@I46*mkW zhF?!?#7$H?(`56;WGeR~?l8Oh&9`Z9x!78K*A(uubt!M+DNP zwvH~J<<95b@+d+L3x1}T&9{pRKQq+ja}W)mgAf0~$zo?oSy`XQ;=Nq!C9u2O>jsd; zjK=Tf@IPp#jZ{OosCV$I8Y91#E^oA1cSfioAuP?DD-h`X6*(PoW&TmxG-hTAVZ<9Z zGOYLX)!Wn8yNkARK8O}9d}d<5f6qR7utRhIN22jp-ebp3E_oe7Eqmx@un_TjfbeCN z@fCH=d(d7b{GvqQ*XngbowV@J{cJ4n-|E>0&X)zcM34(85J|%WD%;tbRb*}GzEQ*5 zzrow_^aHsVm$)UasD||tN@TqEjl!2uWi0W<&!SJc<@=QQDjS_|)+4j4bSlyka#vEA aJ<;5l0%|3g7laY;@3FLkRGx&v+y4Xm-!WhS literal 0 HcmV?d00001 From 0b7e634855a7d7820860026a64bde0af2d841210 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 5 Feb 2024 11:04:50 +0700 Subject: [PATCH 32/98] fix: download model will close panel item hub --- .../ExploreModels/ExploreModelItemHeader/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index 3ffe2cbac..378b9ffa8 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -66,7 +66,15 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null let downloadButton = ( - + ) const onUseModelClick = useCallback(async () => { From 01fec497988f10b25f8f4ea18deb2d875eeb57a5 Mon Sep 17 00:00:00 2001 From: NamH Date: Mon, 5 Feb 2024 13:13:39 +0700 Subject: [PATCH 33/98] fix: reduce the number of api call (#1896) Signed-off-by: James Co-authored-by: James --- core/src/node/api/routes/common.ts | 11 +- core/src/node/api/routes/download.ts | 68 ++--- .../conversational-extension/src/index.ts | 43 ++-- web/containers/Layout/BottomBar/index.tsx | 6 +- .../CommandListDownloadedModel/index.tsx | 6 +- web/containers/Layout/TopBar/index.tsx | 11 +- web/containers/Providers/DataLoader.tsx | 21 ++ web/containers/Providers/EventHandler.tsx | 6 +- web/containers/Providers/EventListener.tsx | 10 +- web/containers/Providers/index.tsx | 6 +- web/helpers/atoms/Assistant.atom.ts | 4 + web/helpers/atoms/Model.atom.ts | 4 + web/hooks/useActiveModel.ts | 4 +- web/hooks/useAssistants.ts | 28 +++ web/hooks/useDeleteModel.ts | 7 +- web/hooks/useGetAssistants.ts | 27 -- web/hooks/useGetConfiguredModels.ts | 30 --- web/hooks/useGetDownloadedModels.ts | 27 -- web/hooks/useGetSystemResources.ts | 2 +- web/hooks/useModels.ts | 46 ++++ web/hooks/useRecommendedModel.ts | 15 +- web/hooks/useSetActiveThread.ts | 55 ++-- web/hooks/useThreads.ts | 26 +- web/screens/Chat/ChatBody/index.tsx | 6 +- web/screens/Chat/CleanThreadModal/index.tsx | 65 +++++ web/screens/Chat/DeleteThreadModal/index.tsx | 68 +++++ .../Chat/RequestDownloadModel/index.tsx | 7 +- web/screens/Chat/ThreadList/index.tsx | 234 ++++-------------- .../ExploreModelItemHeader/index.tsx | 8 +- .../ExploreModels/ModelVersionItem/index.tsx | 6 +- web/screens/ExploreModels/index.tsx | 26 +- web/screens/Settings/Models/index.tsx | 7 +- 32 files changed, 469 insertions(+), 421 deletions(-) create mode 100644 web/containers/Providers/DataLoader.tsx create mode 100644 web/helpers/atoms/Assistant.atom.ts create mode 100644 web/hooks/useAssistants.ts delete mode 100644 web/hooks/useGetAssistants.ts delete mode 100644 web/hooks/useGetConfiguredModels.ts delete mode 100644 web/hooks/useGetDownloadedModels.ts create mode 100644 web/hooks/useModels.ts create mode 100644 web/screens/Chat/CleanThreadModal/index.tsx create mode 100644 web/screens/Chat/DeleteThreadModal/index.tsx diff --git a/core/src/node/api/routes/common.ts b/core/src/node/api/routes/common.ts index 27385e561..8887755fe 100644 --- a/core/src/node/api/routes/common.ts +++ b/core/src/node/api/routes/common.ts @@ -12,6 +12,8 @@ import { import { JanApiRouteConfiguration } from '../common/configuration' import { startModel, stopModel } from '../common/startStopModel' import { ModelSettingParams } from '../../../types' +import { getJanDataFolderPath } from '../../utils' +import { normalizeFilePath } from '../../path' export const commonRouter = async (app: HttpServer) => { // Common Routes @@ -52,7 +54,14 @@ export const commonRouter = async (app: HttpServer) => { // App Routes app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => { const args = JSON.parse(request.body) as any[] - reply.send(JSON.stringify(join(...args[0]))) + + const paths = args[0].map((arg: string) => + typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`)) + ? join(getJanDataFolderPath(), normalizeFilePath(arg)) + : arg + ) + + reply.send(JSON.stringify(join(...paths))) }) app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => { diff --git a/core/src/node/api/routes/download.ts b/core/src/node/api/routes/download.ts index b4e11f957..ab8c0bd37 100644 --- a/core/src/node/api/routes/download.ts +++ b/core/src/node/api/routes/download.ts @@ -4,55 +4,55 @@ import { DownloadManager } from '../../download' import { HttpServer } from '../HttpServer' import { createWriteStream } from 'fs' import { getJanDataFolderPath } from '../../utils' -import { normalizeFilePath } from "../../path"; +import { normalizeFilePath } from '../../path' export const downloadRouter = async (app: HttpServer) => { app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => { - 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 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") { - return join(getJanDataFolderPath(), normalizeFilePath(arg)); + if (typeof arg === 'string' && arg.startsWith('file:')) { + 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 request = require('request') + const progress = require('request-progress') - const rq = request({ url: normalizedArgs[0], strictSSL, proxy }); + 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") { - return join(getJanDataFolderPath(), normalizeFilePath(arg)); + if (typeof arg === 'string' && arg.startsWith('file:')) { + return join(getJanDataFolderPath(), normalizeFilePath(arg)) } - return arg; - }); + return arg + }) - const localPath = normalizedArgs[0]; - const fileName = localPath.split("/").pop() ?? ""; - 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() + }) +} diff --git a/extensions/conversational-extension/src/index.ts b/extensions/conversational-extension/src/index.ts index 3d28a9c1d..bf8c213ad 100644 --- a/extensions/conversational-extension/src/index.ts +++ b/extensions/conversational-extension/src/index.ts @@ -12,7 +12,7 @@ import { * functionality for managing threads. */ export default class JSONConversationalExtension extends ConversationalExtension { - private static readonly _homeDir = 'file://threads' + private static readonly _threadFolder = 'file://threads' private static readonly _threadInfoFileName = 'thread.json' private static readonly _threadMessagesFileName = 'messages.jsonl' @@ -20,8 +20,8 @@ export default class JSONConversationalExtension extends ConversationalExtension * Called when the extension is loaded. */ async onLoad() { - if (!(await fs.existsSync(JSONConversationalExtension._homeDir))) - await fs.mkdirSync(JSONConversationalExtension._homeDir) + if (!(await fs.existsSync(JSONConversationalExtension._threadFolder))) + await fs.mkdirSync(JSONConversationalExtension._threadFolder) console.debug('JSONConversationalExtension loaded') } @@ -68,7 +68,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async saveThread(thread: Thread): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, thread.id, ]) const threadJsonPath = await joinPath([ @@ -92,7 +92,7 @@ export default class JSONConversationalExtension extends ConversationalExtension */ async deleteThread(threadId: string): Promise { const path = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, `${threadId}`, ]) try { @@ -109,7 +109,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async addNewMessage(message: ThreadMessage): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, message.thread_id, ]) const threadMessagePath = await joinPath([ @@ -177,7 +177,7 @@ export default class JSONConversationalExtension extends ConversationalExtension ): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadId, ]) const threadMessagePath = await joinPath([ @@ -205,7 +205,7 @@ export default class JSONConversationalExtension extends ConversationalExtension private async readThread(threadDirName: string): Promise { return fs.readFileSync( await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadDirName, JSONConversationalExtension._threadInfoFileName, ]), @@ -219,14 +219,14 @@ export default class JSONConversationalExtension extends ConversationalExtension */ private async getValidThreadDirs(): Promise { const fileInsideThread: string[] = await fs.readdirSync( - JSONConversationalExtension._homeDir + JSONConversationalExtension._threadFolder ) const threadDirs: string[] = [] for (let i = 0; i < fileInsideThread.length; i++) { if (fileInsideThread[i].includes('.DS_Store')) continue const path = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, fileInsideThread[i], ]) @@ -246,7 +246,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async getAllMessages(threadId: string): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadId, ]) @@ -263,22 +263,17 @@ export default class JSONConversationalExtension extends ConversationalExtension JSONConversationalExtension._threadMessagesFileName, ]) - const result = await fs - .readFileSync(messageFilePath, 'utf-8') - .then((content) => - content - .toString() - .split('\n') - .filter((line) => line !== '') - ) + let readResult = await fs.readFileSync(messageFilePath, 'utf-8') + + if (typeof readResult === 'object') { + readResult = JSON.stringify(readResult) + } + + const result = readResult.split('\n').filter((line) => line !== '') const messages: ThreadMessage[] = [] result.forEach((line: string) => { - try { - messages.push(JSON.parse(line) as ThreadMessage) - } catch (err) { - console.error(err) - } + messages.push(JSON.parse(line)) }) return messages } catch (err) { diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 6e334b9ef..7dc5a9444 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -26,11 +26,12 @@ import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' + import useGetSystemResources from '@/hooks/useGetSystemResources' import { useMainViewState } from '@/hooks/useMainViewState' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' const menuLinks = [ { @@ -49,7 +50,8 @@ const BottomBar = () => { const { activeModel, stateModel } = useActiveModel() const { ram, cpu } = useGetSystemResources() const progress = useAtomValue(appDownloadProgress) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) + const { setMainViewState } = useMainViewState() const { downloadStates } = useDownloadState() const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom) diff --git a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx index 3edce06eb..ac5756e9f 100644 --- a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx +++ b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx @@ -11,7 +11,7 @@ import { Badge, } from '@janhq/uikit' -import { useAtom } from 'jotai' +import { useAtom, useAtomValue } from 'jotai' import { DatabaseIcon, CpuIcon } from 'lucide-react' import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener' @@ -19,14 +19,14 @@ import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener' import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' export default function CommandListDownloadedModel() { const { setMainViewState } = useMainViewState() - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { activeModel, startModel, stopModel } = useActiveModel() const [serverEnabled] = useAtom(serverEnabledAtom) const [showSelectModelModal, setShowSelectModelModal] = useAtom( diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx index f72f5f066..206a9013d 100644 --- a/web/containers/Layout/TopBar/index.tsx +++ b/web/containers/Layout/TopBar/index.tsx @@ -20,7 +20,6 @@ import { MainViewState } from '@/constants/screens' import { useClickOutside } from '@/hooks/useClickOutside' import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants' import { useMainViewState } from '@/hooks/useMainViewState' import { usePath } from '@/hooks/usePath' @@ -29,13 +28,14 @@ import { showRightSideBarAtom } from '@/screens/Chat/Sidebar' import { openFileTitle } from '@/utils/titleUtils' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' const TopBar = () => { const activeThread = useAtomValue(activeThreadAtom) const { mainViewState } = useMainViewState() const { requestCreateNewThread } = useCreateNewThread() - const { assistants } = useGetAssistants() + const assistants = useAtomValue(assistantsAtom) const [showRightSideBar, setShowRightSideBar] = useAtom(showRightSideBarAtom) const [showLeftSideBar, setShowLeftSideBar] = useAtom(showLeftSideBarAtom) const showing = useAtomValue(showRightSideBarAtom) @@ -61,12 +61,7 @@ const TopBar = () => { const onCreateConversationClick = async () => { if (assistants.length === 0) { - const res = await getAssistants() - if (res.length === 0) { - alert('No assistant available') - return - } - requestCreateNewThread(res[0]) + alert('No assistant available') } else { requestCreateNewThread(assistants[0]) } diff --git a/web/containers/Providers/DataLoader.tsx b/web/containers/Providers/DataLoader.tsx new file mode 100644 index 000000000..2b6675d98 --- /dev/null +++ b/web/containers/Providers/DataLoader.tsx @@ -0,0 +1,21 @@ +'use client' + +import { Fragment, ReactNode } from 'react' + +import useAssistants from '@/hooks/useAssistants' +import useModels from '@/hooks/useModels' +import useThreads from '@/hooks/useThreads' + +type Props = { + children: ReactNode +} + +const DataLoader: React.FC = ({ children }) => { + useModels() + useThreads() + useAssistants() + + return {children} +} + +export default DataLoader diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index ec0fbfc90..e9d70d5d2 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -18,7 +18,6 @@ import { loadModelErrorAtom, stateModelAtom, } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { queuedMessageAtom } from '@/hooks/useSendChatMessage' @@ -29,6 +28,7 @@ import { addNewMessageAtom, updateMessageAtom, } from '@/helpers/atoms/ChatMessage.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { updateThreadWaitingForResponseAtom, threadsAtom, @@ -38,7 +38,7 @@ import { export default function EventHandler({ children }: { children: ReactNode }) { const addNewMessage = useSetAtom(addNewMessageAtom) const updateMessage = useSetAtom(updateMessageAtom) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const setActiveModel = useSetAtom(activeModelAtom) const setStateModel = useSetAtom(stateModelAtom) const setQueuedMessage = useSetAtom(queuedMessageAtom) @@ -143,7 +143,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { ?.addNewMessage(message) } }, - [updateMessage, updateThreadWaiting] + [updateMessage, updateThreadWaiting, setIsGeneratingResponse] ) useEffect(() => { diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index 62d4cacb6..5e8556f33 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -3,10 +3,9 @@ import { PropsWithChildren, useEffect, useRef } from 'react' import { baseName } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { modelBinFileName } from '@/utils/model' @@ -14,14 +13,17 @@ import EventHandler from './EventHandler' import { appDownloadProgress } from './Jotai' -import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' +import { + downloadedModelsAtom, + downloadingModelsAtom, +} from '@/helpers/atoms/Model.atom' export default function EventListenerWrapper({ children }: PropsWithChildren) { const setProgress = useSetAtom(appDownloadProgress) const models = useAtomValue(downloadingModelsAtom) const modelsRef = useRef(models) - const { setDownloadedModels, downloadedModels } = useGetDownloadedModels() + const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) const { setDownloadState, setDownloadStateSuccess, diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index c8a20bca7..e7a179ec4 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -23,6 +23,8 @@ import Umami from '@/utils/umami' import Loader from '../Loader' +import DataLoader from './DataLoader' + import KeyListener from './KeyListener' import { extensionManager } from '@/extension' @@ -81,7 +83,9 @@ const Providers = (props: PropsWithChildren) => { - {children} + + {children} + {!isMac && } diff --git a/web/helpers/atoms/Assistant.atom.ts b/web/helpers/atoms/Assistant.atom.ts new file mode 100644 index 000000000..e90923d3d --- /dev/null +++ b/web/helpers/atoms/Assistant.atom.ts @@ -0,0 +1,4 @@ +import { Assistant } from '@janhq/core/.' +import { atom } from 'jotai' + +export const assistantsAtom = atom([]) diff --git a/web/helpers/atoms/Model.atom.ts b/web/helpers/atoms/Model.atom.ts index 6eb7f2ad6..5c9188ad7 100644 --- a/web/helpers/atoms/Model.atom.ts +++ b/web/helpers/atoms/Model.atom.ts @@ -24,3 +24,7 @@ export const removeDownloadingModelAtom = atom( ) } ) + +export const downloadedModelsAtom = atom([]) + +export const configuredModelsAtom = atom([]) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 54a1fdbe0..1b61a0dd1 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -3,9 +3,9 @@ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { toaster } from '@/containers/Toast' -import { useGetDownloadedModels } from './useGetDownloadedModels' import { LAST_USED_MODEL_ID } from './useRecommendedModel' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' export const activeModelAtom = atom(undefined) @@ -21,7 +21,7 @@ export function useActiveModel() { const [activeModel, setActiveModel] = useAtom(activeModelAtom) const activeThread = useAtomValue(activeThreadAtom) const [stateModel, setStateModel] = useAtom(stateModelAtom) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const setLoadModelError = useSetAtom(loadModelErrorAtom) const startModel = async (modelId: string) => { diff --git a/web/hooks/useAssistants.ts b/web/hooks/useAssistants.ts new file mode 100644 index 000000000..8f2c4a92c --- /dev/null +++ b/web/hooks/useAssistants.ts @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +import { Assistant, AssistantExtension, ExtensionTypeEnum } from '@janhq/core' + +import { useSetAtom } from 'jotai' + +import { extensionManager } from '@/extension' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' + +const useAssistants = () => { + const setAssistants = useSetAtom(assistantsAtom) + + useEffect(() => { + const getAssistants = async () => { + const assistants = await getLocalAssistants() + setAssistants(assistants) + } + + getAssistants() + }, [setAssistants]) +} + +const getLocalAssistants = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Assistant) + ?.getAssistants() ?? [] + +export default useAssistants diff --git a/web/hooks/useDeleteModel.ts b/web/hooks/useDeleteModel.ts index fa0cfb45e..d9f2b94be 100644 --- a/web/hooks/useDeleteModel.ts +++ b/web/hooks/useDeleteModel.ts @@ -1,13 +1,14 @@ import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' +import { useAtom } from 'jotai' + import { toaster } from '@/containers/Toast' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' - import { extensionManager } from '@/extension/ExtensionManager' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' export default function useDeleteModel() { - const { setDownloadedModels, downloadedModels } = useGetDownloadedModels() + const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) const deleteModel = async (model: Model) => { await extensionManager diff --git a/web/hooks/useGetAssistants.ts b/web/hooks/useGetAssistants.ts deleted file mode 100644 index 2b34bfbd1..000000000 --- a/web/hooks/useGetAssistants.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect, useState } from 'react' - -import { Assistant, ExtensionTypeEnum, AssistantExtension } from '@janhq/core' - -import { extensionManager } from '@/extension/ExtensionManager' - -export const getAssistants = async (): Promise => - extensionManager - .get(ExtensionTypeEnum.Assistant) - ?.getAssistants() ?? [] - -/** - * Hooks for get assistants - * - * @returns assistants - */ -export default function useGetAssistants() { - const [assistants, setAssistants] = useState([]) - - useEffect(() => { - getAssistants() - .then((data) => setAssistants(data)) - .catch((err) => console.error(err)) - }, []) - - return { assistants } -} diff --git a/web/hooks/useGetConfiguredModels.ts b/web/hooks/useGetConfiguredModels.ts deleted file mode 100644 index 8be052ae2..000000000 --- a/web/hooks/useGetConfiguredModels.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' - -import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' - -import { extensionManager } from '@/extension/ExtensionManager' - -export function useGetConfiguredModels() { - const [loading, setLoading] = useState(false) - const [models, setModels] = useState([]) - - const fetchModels = useCallback(async () => { - setLoading(true) - const models = await getConfiguredModels() - setLoading(false) - setModels(models) - }, []) - - useEffect(() => { - fetchModels() - }, [fetchModels]) - - return { loading, models } -} - -const getConfiguredModels = async (): Promise => { - const models = await extensionManager - .get(ExtensionTypeEnum.Model) - ?.getConfiguredModels() - return models ?? [] -} diff --git a/web/hooks/useGetDownloadedModels.ts b/web/hooks/useGetDownloadedModels.ts deleted file mode 100644 index bba420858..000000000 --- a/web/hooks/useGetDownloadedModels.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect } from 'react' - -import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' - -import { atom, useAtom } from 'jotai' - -import { extensionManager } from '@/extension/ExtensionManager' - -export const downloadedModelsAtom = atom([]) - -export function useGetDownloadedModels() { - const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) - - useEffect(() => { - getDownloadedModels().then((downloadedModels) => { - setDownloadedModels(downloadedModels) - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - return { downloadedModels, setDownloadedModels } -} - -export const getDownloadedModels = async (): Promise => - extensionManager - .get(ExtensionTypeEnum.Model) - ?.getDownloadedModels() ?? [] diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index de595ad7b..3429a93aa 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -58,7 +58,7 @@ export default function useGetSystemResources() { // There is a possibility that this will be removed and replaced by the process event hook? const intervalId = setInterval(() => { getSystemResources() - }, 500) + }, 5000) // clean up interval return () => clearInterval(intervalId) diff --git a/web/hooks/useModels.ts b/web/hooks/useModels.ts new file mode 100644 index 000000000..23e098007 --- /dev/null +++ b/web/hooks/useModels.ts @@ -0,0 +1,46 @@ +import { useEffect } from 'react' + +import { ExtensionTypeEnum, Model, ModelExtension } from '@janhq/core' + +import { useSetAtom } from 'jotai' + +import { extensionManager } from '@/extension' +import { + configuredModelsAtom, + downloadedModelsAtom, +} from '@/helpers/atoms/Model.atom' + +const useModels = () => { + const setDownloadedModels = useSetAtom(downloadedModelsAtom) + const setConfiguredModels = useSetAtom(configuredModelsAtom) + + useEffect(() => { + const getDownloadedModels = async () => { + const models = await getLocalDownloadedModels() + setDownloadedModels(models) + } + + getDownloadedModels() + }, [setDownloadedModels]) + + useEffect(() => { + const getConfiguredModels = async () => { + const models = await getLocalConfiguredModels() + setConfiguredModels(models) + } + + getConfiguredModels() + }, [setConfiguredModels]) +} + +const getLocalConfiguredModels = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Model) + ?.getConfiguredModels() ?? [] + +const getLocalDownloadedModels = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Model) + ?.getDownloadedModels() ?? [] + +export default useModels diff --git a/web/hooks/useRecommendedModel.ts b/web/hooks/useRecommendedModel.ts index 427d2bf73..8122e2b77 100644 --- a/web/hooks/useRecommendedModel.ts +++ b/web/hooks/useRecommendedModel.ts @@ -5,9 +5,9 @@ import { Model, InferenceEngine } from '@janhq/core' import { atom, useAtomValue } from 'jotai' import { activeModelAtom } from './useActiveModel' -import { getDownloadedModels } from './useGetDownloadedModels' -import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' export const lastUsedModel = atom(undefined) @@ -24,19 +24,20 @@ export const LAST_USED_MODEL_ID = 'last-used-model-id' */ export default function useRecommendedModel() { const activeModel = useAtomValue(activeModelAtom) - const [downloadedModels, setDownloadedModels] = useState([]) + const [sortedModels, setSortedModels] = useState([]) const [recommendedModel, setRecommendedModel] = useState() const activeThread = useAtomValue(activeThreadAtom) + const downloadedModels = useAtomValue(downloadedModelsAtom) const getAndSortDownloadedModels = useCallback(async (): Promise => { - const models = (await getDownloadedModels()).sort((a, b) => + const models = downloadedModels.sort((a, b) => a.engine !== InferenceEngine.nitro && b.engine === InferenceEngine.nitro ? 1 : -1 ) - setDownloadedModels(models) + setSortedModels(models) return models - }, []) + }, [downloadedModels]) const getRecommendedModel = useCallback(async (): Promise< Model | undefined @@ -98,5 +99,5 @@ export default function useRecommendedModel() { getRecommendedModel() }, [getRecommendedModel]) - return { recommendedModel, downloadedModels } + return { recommendedModel, downloadedModels: sortedModels } } diff --git a/web/hooks/useSetActiveThread.ts b/web/hooks/useSetActiveThread.ts index f5649ccaf..6cf94d45d 100644 --- a/web/hooks/useSetActiveThread.ts +++ b/web/hooks/useSetActiveThread.ts @@ -1,3 +1,5 @@ +import { useCallback } from 'react' + import { InferenceEvent, ExtensionTypeEnum, @@ -6,7 +8,7 @@ import { ConversationalExtension, } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useSetAtom } from 'jotai' import { loadModelErrorAtom } from './useActiveModel' @@ -14,43 +16,46 @@ import { extensionManager } from '@/extension' import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { ModelParams, - getActiveThreadIdAtom, isGeneratingResponseAtom, setActiveThreadIdAtom, setThreadModelParamsAtom, } from '@/helpers/atoms/Thread.atom' export default function useSetActiveThread() { - const activeThreadId = useAtomValue(getActiveThreadIdAtom) const setActiveThreadId = useSetAtom(setActiveThreadIdAtom) const setThreadMessage = useSetAtom(setConvoMessagesAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) const setLoadModelError = useSetAtom(loadModelErrorAtom) - const setActiveThread = async (thread: Thread) => { - if (activeThreadId === thread.id) { - console.debug('Thread already active') - return - } + const setActiveThread = useCallback( + async (thread: Thread) => { + setIsGeneratingResponse(false) + events.emit(InferenceEvent.OnInferenceStopped, thread.id) - setIsGeneratingResponse(false) - setLoadModelError(undefined) - events.emit(InferenceEvent.OnInferenceStopped, thread.id) + // load the corresponding messages + const messages = await getLocalThreadMessage(thread.id) + setThreadMessage(thread.id, messages) - // load the corresponding messages - const messages = await extensionManager - .get(ExtensionTypeEnum.Conversational) - ?.getAllMessages(thread.id) - setThreadMessage(thread.id, messages ?? []) + setActiveThreadId(thread.id) + const modelParams: ModelParams = { + ...thread.assistants[0]?.model?.parameters, + ...thread.assistants[0]?.model?.settings, + } + setThreadModelParams(thread.id, modelParams) + }, + [ + setActiveThreadId, + setThreadMessage, + setThreadModelParams, + setIsGeneratingResponse, + ] + ) - setActiveThreadId(thread.id) - const modelParams: ModelParams = { - ...thread.assistants[0]?.model?.parameters, - ...thread.assistants[0]?.model?.settings, - } - setThreadModelParams(thread.id, modelParams) - } - - return { activeThreadId, setActiveThread } + return { setActiveThread } } + +const getLocalThreadMessage = async (threadId: string) => + extensionManager + .get(ExtensionTypeEnum.Conversational) + ?.getAllMessages(threadId) ?? [] diff --git a/web/hooks/useThreads.ts b/web/hooks/useThreads.ts index b7de014cc..1ac038b26 100644 --- a/web/hooks/useThreads.ts +++ b/web/hooks/useThreads.ts @@ -1,3 +1,5 @@ +import { useEffect } from 'react' + import { ExtensionTypeEnum, Thread, @@ -5,14 +7,13 @@ import { ConversationalExtension, } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useSetAtom } from 'jotai' import useSetActiveThread from './useSetActiveThread' import { extensionManager } from '@/extension/ExtensionManager' import { ModelParams, - activeThreadAtom, threadModelParamsAtom, threadStatesAtom, threadsAtom, @@ -22,11 +23,10 @@ const useThreads = () => { const setThreadStates = useSetAtom(threadStatesAtom) const setThreads = useSetAtom(threadsAtom) const setThreadModelRuntimeParams = useSetAtom(threadModelParamsAtom) - const activeThread = useAtomValue(activeThreadAtom) const { setActiveThread } = useSetActiveThread() - const getThreads = async () => { - try { + useEffect(() => { + const getThreads = async () => { const localThreads = await getLocalThreads() const localThreadStates: Record = {} const threadModelParams: Record = {} @@ -54,17 +54,19 @@ const useThreads = () => { setThreadStates(localThreadStates) setThreads(localThreads) setThreadModelRuntimeParams(threadModelParams) - if (localThreads.length && !activeThread) { + + if (localThreads.length > 0) { setActiveThread(localThreads[0]) } - } catch (error) { - console.error(error) } - } - return { - getThreads, - } + getThreads() + }, [ + setActiveThread, + setThreadModelRuntimeParams, + setThreadStates, + setThreads, + ]) } const getLocalThreads = async (): Promise => diff --git a/web/screens/Chat/ChatBody/index.tsx b/web/screens/Chat/ChatBody/index.tsx index 66f14d076..c67d6a538 100644 --- a/web/screens/Chat/ChatBody/index.tsx +++ b/web/screens/Chat/ChatBody/index.tsx @@ -11,7 +11,6 @@ import LogoMark from '@/containers/Brand/Logo/Mark' import { MainViewState } from '@/constants/screens' import { loadModelErrorAtom } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' @@ -20,10 +19,13 @@ import ChatItem from '../ChatItem' import ErrorMessage from '../ErrorMessage' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' const ChatBody: React.FC = () => { const messages = useAtomValue(getCurrentChatMessagesAtom) - const { downloadedModels } = useGetDownloadedModels() + + const downloadedModels = useAtomValue(downloadedModelsAtom) + const { setMainViewState } = useMainViewState() if (downloadedModels.length === 0) diff --git a/web/screens/Chat/CleanThreadModal/index.tsx b/web/screens/Chat/CleanThreadModal/index.tsx new file mode 100644 index 000000000..6ef505e6f --- /dev/null +++ b/web/screens/Chat/CleanThreadModal/index.tsx @@ -0,0 +1,65 @@ +import React, { useCallback } from 'react' + +import { + Button, + Modal, + ModalClose, + ModalContent, + ModalFooter, + ModalHeader, + ModalPortal, + ModalTitle, + ModalTrigger, +} from '@janhq/uikit' +import { Paintbrush } from 'lucide-react' + +import useDeleteThread from '@/hooks/useDeleteThread' + +type Props = { + threadId: string +} + +const CleanThreadModal: React.FC = ({ threadId }) => { + const { cleanThread } = useDeleteThread() + const onCleanThreadClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + cleanThread(threadId) + }, + [cleanThread, threadId] + ) + + return ( + + e.stopPropagation()}> +
    + + + Clean thread + +
    +
    + + + + Clean Thread + +

    Are you sure you want to clean this thread?

    + +
    + e.stopPropagation()}> + + + + + +
    +
    +
    +
    + ) +} + +export default React.memo(CleanThreadModal) diff --git a/web/screens/Chat/DeleteThreadModal/index.tsx b/web/screens/Chat/DeleteThreadModal/index.tsx new file mode 100644 index 000000000..edbdb09b4 --- /dev/null +++ b/web/screens/Chat/DeleteThreadModal/index.tsx @@ -0,0 +1,68 @@ +import React, { useCallback } from 'react' + +import { + Modal, + ModalTrigger, + ModalPortal, + ModalContent, + ModalHeader, + ModalTitle, + ModalFooter, + ModalClose, + Button, +} from '@janhq/uikit' +import { Trash2Icon } from 'lucide-react' + +import useDeleteThread from '@/hooks/useDeleteThread' + +type Props = { + threadId: string +} + +const DeleteThreadModal: React.FC = ({ threadId }) => { + const { deleteThread } = useDeleteThread() + const onDeleteThreadClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + deleteThread(threadId) + }, + [deleteThread, threadId] + ) + + return ( + + e.stopPropagation()}> +
    + + + Delete thread + +
    +
    + + + + Delete Thread + +

    + Are you sure you want to delete this thread? This action cannot be + undone. +

    + +
    + e.stopPropagation()}> + + + + + +
    +
    +
    +
    + ) +} + +export default React.memo(DeleteThreadModal) diff --git a/web/screens/Chat/RequestDownloadModel/index.tsx b/web/screens/Chat/RequestDownloadModel/index.tsx index e62dc562d..88fdadd57 100644 --- a/web/screens/Chat/RequestDownloadModel/index.tsx +++ b/web/screens/Chat/RequestDownloadModel/index.tsx @@ -2,15 +2,18 @@ import React, { Fragment, useCallback } from 'react' import { Button } from '@janhq/uikit' +import { useAtomValue } from 'jotai' + import LogoMark from '@/containers/Brand/Logo/Mark' import { MainViewState } from '@/constants/screens' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' + const RequestDownloadModel: React.FC = () => { - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { setMainViewState } = useMainViewState() const onClick = useCallback(() => { diff --git a/web/screens/Chat/ThreadList/index.tsx b/web/screens/Chat/ThreadList/index.tsx index b4a045b1d..8f5bfb8f2 100644 --- a/web/screens/Chat/ThreadList/index.tsx +++ b/web/screens/Chat/ThreadList/index.tsx @@ -1,76 +1,39 @@ -import { useEffect, useState } from 'react' +import { useCallback } from 'react' -import { - Modal, - ModalTrigger, - ModalClose, - ModalFooter, - ModalPortal, - ModalContent, - ModalHeader, - ModalTitle, - Button, -} from '@janhq/uikit' +import { Thread } from '@janhq/core/' import { motion as m } from 'framer-motion' import { useAtomValue } from 'jotai' -import { - GalleryHorizontalEndIcon, - MoreVerticalIcon, - Trash2Icon, - Paintbrush, -} from 'lucide-react' +import { GalleryHorizontalEndIcon, MoreVerticalIcon } from 'lucide-react' import { twMerge } from 'tailwind-merge' -import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import useDeleteThread from '@/hooks/useDeleteThread' - -import useGetAssistants from '@/hooks/useGetAssistants' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import useSetActiveThread from '@/hooks/useSetActiveThread' -import useThreads from '@/hooks/useThreads' - import { displayDate } from '@/utils/datetime' +import CleanThreadModal from '../CleanThreadModal' + +import DeleteThreadModal from '../DeleteThreadModal' + import { - activeThreadAtom, + getActiveThreadIdAtom, threadStatesAtom, threadsAtom, } from '@/helpers/atoms/Thread.atom' export default function ThreadList() { - const threads = useAtomValue(threadsAtom) const threadStates = useAtomValue(threadStatesAtom) - const { getThreads } = useThreads() - const { assistants } = useGetAssistants() - const { requestCreateNewThread } = useCreateNewThread() - const activeThread = useAtomValue(activeThreadAtom) - const { deleteThread, cleanThread } = useDeleteThread() - const { downloadedModels } = useGetDownloadedModels() - const [isThreadsReady, setIsThreadsReady] = useState(false) + const threads = useAtomValue(threadsAtom) + const activeThreadId = useAtomValue(getActiveThreadIdAtom) + const { setActiveThread } = useSetActiveThread() - const { activeThreadId, setActiveThread: onThreadClick } = - useSetActiveThread() - - useEffect(() => { - getThreads().then(() => setIsThreadsReady(true)) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - useEffect(() => { - if ( - isThreadsReady && - downloadedModels.length !== 0 && - threads.length === 0 && - assistants.length !== 0 && - !activeThread - ) { - requestCreateNewThread(assistants[0]) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [assistants, threads, downloadedModels, activeThread, isThreadsReady]) + const onThreadClick = useCallback( + (thread: Thread) => { + setActiveThread(thread) + }, + [setActiveThread] + ) return (
    @@ -83,133 +46,44 @@ export default function ThreadList() {

    No Thread History

    ) : ( - threads.map((thread, i) => { - const lastMessage = - threadStates[thread.id]?.lastMessage ?? 'No new message' - return ( -
    { - onThreadClick(thread) - }} - > -
    -

    - {thread.updated && displayDate(thread.updated)} -

    -

    {thread.title}

    -

    - {lastMessage || 'No new message'} -

    -
    -
    - -
    - - e.stopPropagation()}> -
    - - - Clean thread - -
    -
    - - - - Clean Thread - -

    Are you sure you want to clean this thread?

    - -
    - e.stopPropagation()} - > - - - - - -
    -
    -
    -
    - - e.stopPropagation()}> -
    - - - Delete thread - -
    -
    - - - - Delete Thread - -

    - Are you sure you want to delete this thread? This action - cannot be undone. -

    - -
    - e.stopPropagation()} - > - - - - - -
    -
    -
    -
    -
    -
    - {activeThreadId === thread.id && ( - - )} + threads.map((thread) => ( +
    { + onThreadClick(thread) + }} + > +
    +

    + {thread.updated && displayDate(thread.updated)} +

    +

    {thread.title}

    +

    + {threadStates[thread.id]?.lastMessage ?? 'No new message'} +

    - ) - }) +
    + +
    + + +
    +
    + {activeThreadId === thread.id && ( + + )} +
    + )) )}
    ) diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index 3ffe2cbac..755494ee3 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -27,14 +27,14 @@ import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { getAssistants } from '@/hooks/useGetAssistants' -import { downloadedModelsAtom } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' import { toGibibytes } from '@/utils/converter' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom' type Props = { @@ -49,7 +49,9 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const { modelDownloadStateAtom } = useDownloadState() const { requestCreateNewThread } = useCreateNewThread() const totalRam = useAtomValue(totalRamAtom) + const serverEnabled = useAtomValue(serverEnabledAtom) + const assistants = useAtomValue(assistantsAtom) const downloadAtom = useMemo( () => atom((get) => get(modelDownloadStateAtom)[model.id]), @@ -60,7 +62,6 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const onDownloadClick = useCallback(() => { downloadModel(model) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [model]) const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null @@ -70,7 +71,6 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { ) const onUseModelClick = useCallback(async () => { - const assistants = await getAssistants() if (assistants.length === 0) { alert('No assistant available') return diff --git a/web/screens/ExploreModels/ModelVersionItem/index.tsx b/web/screens/ExploreModels/ModelVersionItem/index.tsx index 50d71b161..3a9385670 100644 --- a/web/screens/ExploreModels/ModelVersionItem/index.tsx +++ b/web/screens/ExploreModels/ModelVersionItem/index.tsx @@ -10,9 +10,11 @@ import { MainViewState } from '@/constants/screens' import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' + import { useMainViewState } from '@/hooks/useMainViewState' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' + type Props = { model: Model isRecommended: boolean @@ -20,7 +22,7 @@ type Props = { const ModelVersionItem: React.FC = ({ model }) => { const { downloadModel } = useDownloadModel() - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { setMainViewState } = useMainViewState() const isDownloaded = downloadedModels.find( diff --git a/web/screens/ExploreModels/index.tsx b/web/screens/ExploreModels/index.tsx index 398b2db08..7002c60b7 100644 --- a/web/screens/ExploreModels/index.tsx +++ b/web/screens/ExploreModels/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useCallback, useState } from 'react' import { openExternalUrl } from '@janhq/core' import { @@ -12,24 +12,24 @@ import { SelectItem, } from '@janhq/uikit' +import { useAtomValue } from 'jotai' import { SearchIcon } from 'lucide-react' -import Loader from '@/containers/Loader' - -import { useGetConfiguredModels } from '@/hooks/useGetConfiguredModels' - -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' - import ExploreModelList from './ExploreModelList' +import { + configuredModelsAtom, + downloadedModelsAtom, +} from '@/helpers/atoms/Model.atom' + const ExploreModelsScreen = () => { - const { loading, models } = useGetConfiguredModels() + const configuredModels = useAtomValue(configuredModelsAtom) + const downloadedModels = useAtomValue(downloadedModelsAtom) const [searchValue, setsearchValue] = useState('') - const { downloadedModels } = useGetDownloadedModels() const [sortSelected, setSortSelected] = useState('All Models') const sortMenu = ['All Models', 'Recommended', 'Downloaded'] - const filteredModels = models.filter((x) => { + const filteredModels = configuredModels.filter((x) => { if (sortSelected === 'Downloaded') { return ( x.name.toLowerCase().includes(searchValue.toLowerCase()) && @@ -45,11 +45,9 @@ const ExploreModelsScreen = () => { } }) - const onHowToImportModelClick = () => { + const onHowToImportModelClick = useCallback(() => { openExternalUrl('https://jan.ai/guides/using-models/import-manually/') - } - - if (loading) return + }, []) return (
    Date: Tue, 6 Feb 2024 17:31:46 +0700 Subject: [PATCH 38/98] feat: User Selectable GPUs and GPU-based Model Recommendations (#1730) --- .../src/node/execute.ts | 16 ++--- .../src/node/nvidia.ts | 63 +++++++++---------- extensions/monitoring-extension/package.json | 5 +- extensions/monitoring-extension/src/module.ts | 54 +++++++++++++--- web/containers/Layout/BottomBar/index.tsx | 20 +++++- web/helpers/atoms/SystemBar.atom.ts | 2 + web/hooks/useGetSystemResources.ts | 20 +++++- web/hooks/useSettings.ts | 3 + .../ExploreModelItemHeader/index.tsx | 14 ++++- web/screens/Settings/Advanced/index.tsx | 57 ++++++++++++++++- 10 files changed, 196 insertions(+), 58 deletions(-) diff --git a/extensions/inference-nitro-extension/src/node/execute.ts b/extensions/inference-nitro-extension/src/node/execute.ts index ca266639c..83b5226d4 100644 --- a/extensions/inference-nitro-extension/src/node/execute.ts +++ b/extensions/inference-nitro-extension/src/node/execute.ts @@ -25,12 +25,12 @@ export const executableNitroFile = (): NitroExecutableOptions => { if (nvidiaInfo["run_mode"] === "cpu") { binaryFolder = path.join(binaryFolder, "win-cpu"); } else { - if (nvidiaInfo["cuda"].version === "12") { - binaryFolder = path.join(binaryFolder, "win-cuda-12-0"); - } else { + if (nvidiaInfo["cuda"].version === "11") { binaryFolder = path.join(binaryFolder, "win-cuda-11-7"); + } else { + binaryFolder = path.join(binaryFolder, "win-cuda-12-0"); } - cudaVisibleDevices = nvidiaInfo["gpu_highest_vram"]; + cudaVisibleDevices = nvidiaInfo["gpus_in_use"].join(","); } binaryName = "nitro.exe"; } else if (process.platform === "darwin") { @@ -50,12 +50,12 @@ export const executableNitroFile = (): NitroExecutableOptions => { if (nvidiaInfo["run_mode"] === "cpu") { binaryFolder = path.join(binaryFolder, "linux-cpu"); } else { - if (nvidiaInfo["cuda"].version === "12") { - binaryFolder = path.join(binaryFolder, "linux-cuda-12-0"); - } else { + if (nvidiaInfo["cuda"].version === "11") { binaryFolder = path.join(binaryFolder, "linux-cuda-11-7"); + } else { + binaryFolder = path.join(binaryFolder, "linux-cuda-12-0"); } - cudaVisibleDevices = nvidiaInfo["gpu_highest_vram"]; + cudaVisibleDevices = nvidiaInfo["gpus_in_use"].join(","); } } return { diff --git a/extensions/inference-nitro-extension/src/node/nvidia.ts b/extensions/inference-nitro-extension/src/node/nvidia.ts index 13e43290b..bed2856a1 100644 --- a/extensions/inference-nitro-extension/src/node/nvidia.ts +++ b/extensions/inference-nitro-extension/src/node/nvidia.ts @@ -19,6 +19,8 @@ const DEFALT_SETTINGS = { }, gpus: [], gpu_highest_vram: "", + gpus_in_use: [], + is_initial: true, }; /** @@ -48,11 +50,15 @@ export interface NitroProcessInfo { */ export async function updateNvidiaInfo() { if (process.platform !== "darwin") { - await Promise.all([ - updateNvidiaDriverInfo(), - updateCudaExistence(), - updateGpuInfo(), - ]); + let data; + try { + data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); + } catch (error) { + data = DEFALT_SETTINGS; + writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2)); + } + updateNvidiaDriverInfo(); + updateGpuInfo(); } } @@ -73,12 +79,7 @@ export async function updateNvidiaDriverInfo(): Promise { exec( "nvidia-smi --query-gpu=driver_version --format=csv,noheader", (error, stdout) => { - let data; - try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); - } catch (error) { - data = DEFALT_SETTINGS; - } + let data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); if (!error) { const firstLine = stdout.split("\n")[0].trim(); @@ -107,7 +108,7 @@ export function checkFileExistenceInPaths( /** * Validate cuda for linux and windows */ -export function updateCudaExistence() { +export function updateCudaExistence(data: Record = DEFALT_SETTINGS): Record { let filesCuda12: string[]; let filesCuda11: string[]; let paths: string[]; @@ -141,19 +142,14 @@ export function updateCudaExistence() { cudaVersion = "12"; } - let data; - try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); - } catch (error) { - data = DEFALT_SETTINGS; - } - data["cuda"].exist = cudaExists; data["cuda"].version = cudaVersion; - if (cudaExists) { + console.log(data["is_initial"], data["gpus_in_use"]); + if (cudaExists && data["is_initial"] && data["gpus_in_use"].length > 0) { data.run_mode = "gpu"; } - writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2)); + data.is_initial = false; + return data; } /** @@ -161,14 +157,9 @@ export function updateCudaExistence() { */ export async function updateGpuInfo(): Promise { exec( - "nvidia-smi --query-gpu=index,memory.total --format=csv,noheader,nounits", + "nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits", (error, stdout) => { - let data; - try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); - } catch (error) { - data = DEFALT_SETTINGS; - } + let data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); if (!error) { // Get GPU info and gpu has higher memory first @@ -178,21 +169,27 @@ export async function updateGpuInfo(): Promise { .trim() .split("\n") .map((line) => { - let [id, vram] = line.split(", "); + let [id, vram, name] = line.split(", "); vram = vram.replace(/\r/g, ""); if (parseFloat(vram) > highestVram) { highestVram = parseFloat(vram); highestVramId = id; } - return { id, vram }; + return { id, vram, name }; }); - data["gpus"] = gpus; - data["gpu_highest_vram"] = highestVramId; + data.gpus = gpus; + data.gpu_highest_vram = highestVramId; } else { - data["gpus"] = []; + data.gpus = []; + data.gpu_highest_vram = ""; } + if (!data["gpus_in_use"] || data["gpus_in_use"].length === 0) { + data.gpus_in_use = [data["gpu_highest_vram"]]; + } + + data = updateCudaExistence(data); writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2)); Promise.resolve(); } diff --git a/extensions/monitoring-extension/package.json b/extensions/monitoring-extension/package.json index 9935e536e..20d3c485f 100644 --- a/extensions/monitoring-extension/package.json +++ b/extensions/monitoring-extension/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/monitoring-extension", - "version": "1.0.9", + "version": "1.0.10", "description": "This extension provides system health and OS level data", "main": "dist/index.js", "module": "dist/module.js", @@ -26,6 +26,7 @@ "README.md" ], "bundleDependencies": [ - "node-os-utils" + "node-os-utils", + "@janhq/core" ] } diff --git a/extensions/monitoring-extension/src/module.ts b/extensions/monitoring-extension/src/module.ts index 86b553d52..2c1b14343 100644 --- a/extensions/monitoring-extension/src/module.ts +++ b/extensions/monitoring-extension/src/module.ts @@ -1,4 +1,14 @@ const nodeOsUtils = require("node-os-utils"); +const getJanDataFolderPath = require("@janhq/core/node").getJanDataFolderPath; +const path = require("path"); +const { readFileSync } = require("fs"); +const exec = require("child_process").exec; + +const NVIDIA_INFO_FILE = path.join( + getJanDataFolderPath(), + "settings", + "settings.json" +); const getResourcesInfo = () => new Promise((resolve) => { @@ -16,18 +26,48 @@ const getResourcesInfo = () => }); const getCurrentLoad = () => - new Promise((resolve) => { + new Promise((resolve, reject) => { nodeOsUtils.cpu.usage().then((cpuPercentage) => { - const response = { - cpu: { - usage: cpuPercentage, - }, + let data = { + run_mode: "cpu", + gpus_in_use: [], }; - resolve(response); + if (process.platform !== "darwin") { + data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); + } + if (data.run_mode === "gpu" && data.gpus_in_use.length > 0) { + const gpuIds = data["gpus_in_use"].join(","); + if (gpuIds !== "") { + exec( + `nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.total,memory.free,utilization.memory --format=csv,noheader,nounits --id=${gpuIds}`, + (error, stdout, stderr) => { + if (error) { + console.error(`exec error: ${error}`); + reject(error); + return; + } + const gpuInfo = stdout.trim().split("\n").map((line) => { + const [id, name, temperature, utilization, memoryTotal, memoryFree, memoryUtilization] = line.split(", ").map(item => item.replace(/\r/g, "")); + return { id, name, temperature, utilization, memoryTotal, memoryFree, memoryUtilization }; + }); + resolve({ + cpu: { usage: cpuPercentage }, + gpu: gpuInfo + }); + } + ); + } else { + // Handle the case where gpuIds is empty + resolve({ cpu: { usage: cpuPercentage }, gpu: [] }); + } + } else { + // Handle the case where run_mode is not 'gpu' or no GPUs are in use + resolve({ cpu: { usage: cpuPercentage }, gpu: [] }); + } }); }); module.exports = { getResourcesInfo, getCurrentLoad, -}; +}; \ No newline at end of file diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 7dc5a9444..32dc70c70 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -48,7 +48,7 @@ const menuLinks = [ const BottomBar = () => { const { activeModel, stateModel } = useActiveModel() - const { ram, cpu } = useGetSystemResources() + const { ram, cpu, gpus } = useGetSystemResources() const progress = useAtomValue(appDownloadProgress) const downloadedModels = useAtomValue(downloadedModelsAtom) @@ -57,6 +57,13 @@ const BottomBar = () => { const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom) const [serverEnabled] = useAtom(serverEnabledAtom) + const calculateGpuMemoryUsage = (gpu: Record) => { + const total = parseInt(gpu.memoryTotal) + const free = parseInt(gpu.memoryFree) + if (!total || !free) return 0 + return Math.round(((total - free) / total) * 100) + } + return (
    @@ -119,6 +126,17 @@ const BottomBar = () => {
    + {gpus.length > 0 && ( +
    + {gpus.map((gpu, index) => ( + + ))} +
    + )} {/* VERSION is defined by webpack, please see next.config.js */} Jan v{VERSION ?? ''} diff --git a/web/helpers/atoms/SystemBar.atom.ts b/web/helpers/atoms/SystemBar.atom.ts index 42ef7b29f..22a7573ec 100644 --- a/web/helpers/atoms/SystemBar.atom.ts +++ b/web/helpers/atoms/SystemBar.atom.ts @@ -5,3 +5,5 @@ export const usedRamAtom = atom(0) export const availableRamAtom = atom(0) export const cpuUsageAtom = atom(0) + +export const nvidiaTotalVramAtom = atom(0) diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index 3429a93aa..3f71040d7 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -10,15 +10,19 @@ import { cpuUsageAtom, totalRamAtom, usedRamAtom, + nvidiaTotalVramAtom, } from '@/helpers/atoms/SystemBar.atom' export default function useGetSystemResources() { const [ram, setRam] = useState(0) const [cpu, setCPU] = useState(0) + + const [gpus, setGPUs] = useState[]>([]) const setTotalRam = useSetAtom(totalRamAtom) const setUsedRam = useSetAtom(usedRamAtom) const setAvailableRam = useSetAtom(availableRamAtom) const setCpuUsage = useSetAtom(cpuUsageAtom) + const setTotalNvidiaVram = useSetAtom(nvidiaTotalVramAtom) const getSystemResources = async () => { if ( @@ -48,12 +52,25 @@ export default function useGetSystemResources() { ) setCPU(Math.round(currentLoadInfor?.cpu?.usage ?? 0)) setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0)) + + const gpus = currentLoadInfor?.gpu ?? [] + setGPUs(gpus) + + let totalNvidiaVram = 0 + if (gpus.length > 0) { + totalNvidiaVram = gpus.reduce( + (total: number, gpu: { memoryTotal: string }) => + total + Number(gpu.memoryTotal), + 0 + ) + } + setTotalNvidiaVram(totalNvidiaVram) } useEffect(() => { getSystemResources() - // Fetch interval - every 0.5s + // Fetch interval - every 2s // TODO: Will we really need this? // There is a possibility that this will be removed and replaced by the process event hook? const intervalId = setInterval(() => { @@ -69,5 +86,6 @@ export default function useGetSystemResources() { totalRamAtom, ram, cpu, + gpus, } } diff --git a/web/hooks/useSettings.ts b/web/hooks/useSettings.ts index 168e72489..289355b36 100644 --- a/web/hooks/useSettings.ts +++ b/web/hooks/useSettings.ts @@ -47,14 +47,17 @@ export const useSettings = () => { const saveSettings = async ({ runMode, notify, + gpusInUse, }: { runMode?: string | undefined notify?: boolean | undefined + gpusInUse?: string[] | undefined }) => { const settingsFile = await joinPath(['file://settings', 'settings.json']) const settings = await readSettings() if (runMode != null) settings.run_mode = runMode if (notify != null) settings.notify = notify + if (gpusInUse != null) settings.gpus_in_use = gpusInUse await fs.writeFileSync(settingsFile, JSON.stringify(settings)) } diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index 5faa772c7..17b897d51 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -35,7 +35,10 @@ import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' -import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom' +import { + nvidiaTotalVramAtom, + totalRamAtom, +} from '@/helpers/atoms/SystemBar.atom' type Props = { model: Model @@ -49,7 +52,12 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const { modelDownloadStateAtom } = useDownloadState() const { requestCreateNewThread } = useCreateNewThread() const totalRam = useAtomValue(totalRamAtom) - + const nvidiaTotalVram = useAtomValue(nvidiaTotalVramAtom) + // Default nvidia returns vram in MB, need to convert to bytes to match the unit of totalRamW + let ram = nvidiaTotalVram * 1024 * 1024 + if (ram === 0) { + ram = totalRam + } const serverEnabled = useAtomValue(serverEnabledAtom) const assistants = useAtomValue(assistantsAtom) @@ -115,7 +123,7 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { } const getLabel = (size: number) => { - if (size * 1.25 >= totalRam) { + if (size * 1.25 >= ram) { return ( Not enough RAM diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index d2f7d81ee..f6c8fb4d8 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -33,7 +33,10 @@ const Advanced = () => { } = useContext(FeatureToggleContext) const [partialProxy, setPartialProxy] = useState(proxy) const [gpuEnabled, setGpuEnabled] = useState(false) - + const [gpuList, setGpuList] = useState([ + { id: 'none', vram: null, name: 'none' }, + ]) + const [gpusInUse, setGpusInUse] = useState([]) const { readSettings, saveSettings, validateSettings, setShowNotification } = useSettings() @@ -54,6 +57,10 @@ const Advanced = () => { const setUseGpuIfPossible = async () => { const settings = await readSettings() setGpuEnabled(settings.run_mode === 'gpu') + setGpusInUse(settings.gpus_in_use || []) + if (settings.gpus) { + setGpuList(settings.gpus) + } } setUseGpuIfPossible() }, [readSettings]) @@ -69,6 +76,20 @@ const Advanced = () => { }) } + const handleGPUChange = (gpuId: string) => { + let updatedGpusInUse = [...gpusInUse] + if (updatedGpusInUse.includes(gpuId)) { + updatedGpusInUse = updatedGpusInUse.filter((id) => id !== gpuId) + if (gpuEnabled && updatedGpusInUse.length === 0) { + updatedGpusInUse.push(gpuId) + } + } else { + updatedGpusInUse.push(gpuId) + } + setGpusInUse(updatedGpusInUse) + saveSettings({ gpusInUse: updatedGpusInUse }) + } + return (
    {/* Keyboard shortcut */} @@ -133,10 +154,40 @@ const Advanced = () => { />
    )} - {/* Directory */} + {gpuEnabled && ( +
    + +
    + {gpuList.map((gpu) => ( +
    + handleGPUChange(gpu.id)} + /> + +
    + ))} +
    +
    + )} + {/* Warning message */} + {gpuEnabled && gpusInUse.length > 1 && ( +

    + If enabling multi-GPU without the same GPU model or without NVLink, it + may affect token speed. +

    + )} - {/* Proxy */}
    From a8cd9724ef07f9add359d60906f6f7ca4f76c6f4 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 6 Feb 2024 22:46:48 +0700 Subject: [PATCH 39/98] fix: markdown render for chat completion role user (#1944) --- web/screens/Chat/SimpleTextMessage/index.tsx | 56 +++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/web/screens/Chat/SimpleTextMessage/index.tsx b/web/screens/Chat/SimpleTextMessage/index.tsx index 261bb3497..9be45e7e6 100644 --- a/web/screens/Chat/SimpleTextMessage/index.tsx +++ b/web/screens/Chat/SimpleTextMessage/index.tsx @@ -18,7 +18,7 @@ import hljs from 'highlight.js' import { useAtomValue } from 'jotai' import { FolderOpenIcon } from 'lucide-react' -import { Marked, Renderer } from 'marked' +import { Marked, Renderer, marked as markedDefault } from 'marked' import { markedHighlight } from 'marked-highlight' @@ -37,13 +37,29 @@ import MessageToolbar from '../MessageToolbar' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' +function isMarkdownValue(value: string): boolean { + const tokenTypes: string[] = [] + markedDefault(value, { + walkTokens: (token) => { + tokenTypes.push(token.type) + }, + }) + const isMarkdown = ['code', 'codespan'].some((tokenType) => { + return tokenTypes.includes(tokenType) + }) + return isMarkdown +} + const SimpleTextMessage: React.FC = (props) => { let text = '' + const isUser = props.role === ChatCompletionRole.User + const isSystem = props.role === ChatCompletionRole.System + if (props.content && props.content.length > 0) { text = props.content[0]?.text?.value ?? '' } + const clipboard = useClipboard({ timeout: 1000 }) - const { onViewFile, onViewFileContainer } = usePath() const marked: Marked = new Marked( markedHighlight({ @@ -88,9 +104,8 @@ const SimpleTextMessage: React.FC = (props) => { } ) + const { onViewFile, onViewFileContainer } = usePath() const parsedText = marked.parse(text) - const isUser = props.role === ChatCompletionRole.User - const isSystem = props.role === ChatCompletionRole.System const [tokenCount, setTokenCount] = useState(0) const [lastTimestamp, setLastTimestamp] = useState() const [tokenSpeed, setTokenSpeed] = useState(0) @@ -260,16 +275,29 @@ const SimpleTextMessage: React.FC = (props) => {
    )} -
    + {isUser && !isMarkdownValue(text) ? ( +
    + {text} +
    + ) : ( +
    + )}
    From 823f8e09973c997735bbbe9c7631faba1e987678 Mon Sep 17 00:00:00 2001 From: Service Account Date: Tue, 6 Feb 2024 20:20:11 +0000 Subject: [PATCH 40/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b84e03e7f..e1f74ef23 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 2f961d7cabcfa025c6da3cb2b4597edb01ceed43 Mon Sep 17 00:00:00 2001 From: Van Pham <64197333+Van-QA@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:41:35 +0700 Subject: [PATCH 41/98] feat: Playwright capture screenshot of Electron desktop app (Jan) on failures (#1934) * feat: Apply Screenshot on failures * feat: set timeout by default * chore: clean up import --- electron/playwright.config.ts | 9 ++- electron/tests/e2e/hub.e2e.spec.ts | 34 ++++++++++++ electron/tests/e2e/navigation.e2e.spec.ts | 38 +++++++++++++ electron/tests/e2e/settings.e2e.spec.ts | 23 ++++++++ electron/tests/hub.e2e.spec.ts | 48 ---------------- electron/tests/navigation.e2e.spec.ts | 61 --------------------- electron/tests/pages/basePage.ts | 67 +++++++++++++++++++++++ electron/tests/settings.e2e.spec.ts | 45 --------------- 8 files changed, 170 insertions(+), 155 deletions(-) create mode 100644 electron/tests/e2e/hub.e2e.spec.ts create mode 100644 electron/tests/e2e/navigation.e2e.spec.ts create mode 100644 electron/tests/e2e/settings.e2e.spec.ts delete mode 100644 electron/tests/hub.e2e.spec.ts delete mode 100644 electron/tests/navigation.e2e.spec.ts create mode 100644 electron/tests/pages/basePage.ts delete mode 100644 electron/tests/settings.e2e.spec.ts diff --git a/electron/playwright.config.ts b/electron/playwright.config.ts index 1fa3313f2..8047b7513 100644 --- a/electron/playwright.config.ts +++ b/electron/playwright.config.ts @@ -1,9 +1,16 @@ import { PlaywrightTestConfig } from '@playwright/test' const config: PlaywrightTestConfig = { - testDir: './tests', + testDir: './tests/e2e', retries: 0, globalTimeout: 300000, + use: { + screenshot: 'only-on-failure', + video: 'retain-on-failure', + trace: 'retain-on-failure', + }, + + reporter: [['html', { outputFolder: './playwright-report' }]], } export default config diff --git a/electron/tests/e2e/hub.e2e.spec.ts b/electron/tests/e2e/hub.e2e.spec.ts new file mode 100644 index 000000000..68632058e --- /dev/null +++ b/electron/tests/e2e/hub.e2e.spec.ts @@ -0,0 +1,34 @@ +import { + page, + test, + setupElectron, + teardownElectron, + TIMEOUT, +} from '../pages/basePage' +import { expect } from '@playwright/test' + +test.beforeAll(async () => { + const appInfo = await setupElectron() + expect(appInfo.asar).toBe(true) + expect(appInfo.executable).toBeTruthy() + expect(appInfo.main).toBeTruthy() + expect(appInfo.name).toBe('jan') + expect(appInfo.packageJson).toBeTruthy() + expect(appInfo.packageJson.name).toBe('jan') + expect(appInfo.platform).toBeTruthy() + expect(appInfo.platform).toBe(process.platform) + expect(appInfo.resourcesDir).toBeTruthy() +}) + +test.afterAll(async () => { + await teardownElectron() +}) + +test('explores hub', async () => { + await page.getByTestId('Hub').first().click({ + timeout: TIMEOUT, + }) + await page.getByTestId('hub-container-test-id').isVisible({ + timeout: TIMEOUT, + }) +}) diff --git a/electron/tests/e2e/navigation.e2e.spec.ts b/electron/tests/e2e/navigation.e2e.spec.ts new file mode 100644 index 000000000..2da59953c --- /dev/null +++ b/electron/tests/e2e/navigation.e2e.spec.ts @@ -0,0 +1,38 @@ +import { expect } from '@playwright/test' +import { + page, + setupElectron, + TIMEOUT, + test, + teardownElectron, +} from '../pages/basePage' + +test.beforeAll(async () => { + await setupElectron() +}) + +test.afterAll(async () => { + await teardownElectron() +}) + +test('renders left navigation panel', async () => { + const systemMonitorBtn = await page + .getByTestId('System Monitor') + .first() + .isEnabled({ + timeout: TIMEOUT, + }) + const settingsBtn = await page + .getByTestId('Thread') + .first() + .isEnabled({ timeout: TIMEOUT }) + expect([systemMonitorBtn, settingsBtn].filter((e) => !e).length).toBe(0) + // Chat section should be there + await page.getByTestId('Local API Server').first().click({ + timeout: TIMEOUT, + }) + const localServer = page.getByTestId('local-server-testid').first() + await expect(localServer).toBeVisible({ + timeout: TIMEOUT, + }) +}) diff --git a/electron/tests/e2e/settings.e2e.spec.ts b/electron/tests/e2e/settings.e2e.spec.ts new file mode 100644 index 000000000..54215d9b1 --- /dev/null +++ b/electron/tests/e2e/settings.e2e.spec.ts @@ -0,0 +1,23 @@ +import { expect } from '@playwright/test' + +import { + setupElectron, + teardownElectron, + test, + page, + TIMEOUT, +} from '../pages/basePage' + +test.beforeAll(async () => { + await setupElectron() +}) + +test.afterAll(async () => { + await teardownElectron() +}) + +test('shows settings', async () => { + await page.getByTestId('Settings').first().click({ timeout: TIMEOUT }) + const settingDescription = page.getByTestId('testid-setting-description') + await expect(settingDescription).toBeVisible({ timeout: TIMEOUT }) +}) diff --git a/electron/tests/hub.e2e.spec.ts b/electron/tests/hub.e2e.spec.ts deleted file mode 100644 index cc72e037e..000000000 --- a/electron/tests/hub.e2e.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { _electron as electron } from 'playwright' -import { ElectronApplication, Page, expect, test } from '@playwright/test' - -import { - findLatestBuild, - parseElectronApp, - stubDialog, -} from 'electron-playwright-helpers' - -let electronApp: ElectronApplication -let page: Page -const TIMEOUT: number = parseInt(process.env.TEST_TIMEOUT || '300000') - -test.beforeAll(async () => { - process.env.CI = 'e2e' - - const latestBuild = findLatestBuild('dist') - expect(latestBuild).toBeTruthy() - - // parse the packaged Electron app and find paths and other info - const appInfo = parseElectronApp(latestBuild) - expect(appInfo).toBeTruthy() - - electronApp = await electron.launch({ - args: [appInfo.main], // main file from package.json - executablePath: appInfo.executable, // path to the Electron executable - }) - await stubDialog(electronApp, 'showMessageBox', { response: 1 }) - - page = await electronApp.firstWindow({ - timeout: TIMEOUT, - }) -}) - -test.afterAll(async () => { - await electronApp.close() - await page.close() -}) - -test('explores hub', async () => { - test.setTimeout(TIMEOUT) - await page.getByTestId('Hub').first().click({ - timeout: TIMEOUT, - }) - await page.getByTestId('hub-container-test-id').isVisible({ - timeout: TIMEOUT, - }) -}) diff --git a/electron/tests/navigation.e2e.spec.ts b/electron/tests/navigation.e2e.spec.ts deleted file mode 100644 index 5c8721c2f..000000000 --- a/electron/tests/navigation.e2e.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { _electron as electron } from 'playwright' -import { ElectronApplication, Page, expect, test } from '@playwright/test' - -import { - findLatestBuild, - parseElectronApp, - stubDialog, -} from 'electron-playwright-helpers' - -let electronApp: ElectronApplication -let page: Page -const TIMEOUT: number = parseInt(process.env.TEST_TIMEOUT || '300000') - -test.beforeAll(async () => { - process.env.CI = 'e2e' - - const latestBuild = findLatestBuild('dist') - expect(latestBuild).toBeTruthy() - - // parse the packaged Electron app and find paths and other info - const appInfo = parseElectronApp(latestBuild) - expect(appInfo).toBeTruthy() - - electronApp = await electron.launch({ - args: [appInfo.main], // main file from package.json - executablePath: appInfo.executable, // path to the Electron executable - }) - await stubDialog(electronApp, 'showMessageBox', { response: 1 }) - - page = await electronApp.firstWindow({ - timeout: TIMEOUT, - }) -}) - -test.afterAll(async () => { - await electronApp.close() - await page.close() -}) - -test('renders left navigation panel', async () => { - test.setTimeout(TIMEOUT) - const systemMonitorBtn = await page - .getByTestId('System Monitor') - .first() - .isEnabled({ - timeout: TIMEOUT, - }) - const settingsBtn = await page - .getByTestId('Thread') - .first() - .isEnabled({ timeout: TIMEOUT }) - expect([systemMonitorBtn, settingsBtn].filter((e) => !e).length).toBe(0) - // Chat section should be there - await page.getByTestId('Local API Server').first().click({ - timeout: TIMEOUT, - }) - const localServer = await page.getByTestId('local-server-testid').first() - await expect(localServer).toBeVisible({ - timeout: TIMEOUT, - }) -}) diff --git a/electron/tests/pages/basePage.ts b/electron/tests/pages/basePage.ts new file mode 100644 index 000000000..5f1a6fca1 --- /dev/null +++ b/electron/tests/pages/basePage.ts @@ -0,0 +1,67 @@ +import { + expect, + test as base, + _electron as electron, + ElectronApplication, + Page, +} from '@playwright/test' +import { + findLatestBuild, + parseElectronApp, + stubDialog, +} from 'electron-playwright-helpers' + +export const TIMEOUT: number = parseInt(process.env.TEST_TIMEOUT || '300000') + +export let electronApp: ElectronApplication +export let page: Page + +export async function setupElectron() { + process.env.CI = 'e2e' + + const latestBuild = findLatestBuild('dist') + expect(latestBuild).toBeTruthy() + + // parse the packaged Electron app and find paths and other info + const appInfo = parseElectronApp(latestBuild) + expect(appInfo).toBeTruthy() + + electronApp = await electron.launch({ + args: [appInfo.main], // main file from package.json + executablePath: appInfo.executable, // path to the Electron executable + }) + await stubDialog(electronApp, 'showMessageBox', { response: 1 }) + + page = await electronApp.firstWindow({ + timeout: TIMEOUT, + }) + // Return appInfo for future use + return appInfo +} + +export async function teardownElectron() { + await page.close() + await electronApp.close() +} + +export const test = base.extend<{ + attachScreenshotsToReport: void +}>({ + attachScreenshotsToReport: [ + async ({ request }, use, testInfo) => { + await use() + + // After the test, we can check whether the test passed or failed. + if (testInfo.status !== testInfo.expectedStatus) { + const screenshot = await page.screenshot() + await testInfo.attach('screenshot', { + body: screenshot, + contentType: 'image/png', + }) + } + }, + { auto: true }, + ], +}) + +test.setTimeout(TIMEOUT) diff --git a/electron/tests/settings.e2e.spec.ts b/electron/tests/settings.e2e.spec.ts deleted file mode 100644 index ad2d7b4a4..000000000 --- a/electron/tests/settings.e2e.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { _electron as electron } from 'playwright' -import { ElectronApplication, Page, expect, test } from '@playwright/test' - -import { - findLatestBuild, - parseElectronApp, - stubDialog, -} from 'electron-playwright-helpers' - -let electronApp: ElectronApplication -let page: Page -const TIMEOUT: number = parseInt(process.env.TEST_TIMEOUT || '300000') - -test.beforeAll(async () => { - process.env.CI = 'e2e' - - const latestBuild = findLatestBuild('dist') - expect(latestBuild).toBeTruthy() - - // parse the packaged Electron app and find paths and other info - const appInfo = parseElectronApp(latestBuild) - expect(appInfo).toBeTruthy() - - electronApp = await electron.launch({ - args: [appInfo.main], // main file from package.json - executablePath: appInfo.executable, // path to the Electron executable - }) - await stubDialog(electronApp, 'showMessageBox', { response: 1 }) - - page = await electronApp.firstWindow({ - timeout: TIMEOUT, - }) -}) - -test.afterAll(async () => { - await electronApp.close() - await page.close() -}) - -test('shows settings', async () => { - test.setTimeout(TIMEOUT) - await page.getByTestId('Settings').first().click({ timeout: TIMEOUT }) - const settingDescription = page.getByTestId('testid-setting-description') - await expect(settingDescription).toBeVisible({ timeout: TIMEOUT }) -}) From e6ea11e860a192839616a2de8293eb8edec7291e Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 7 Feb 2024 11:55:32 +0700 Subject: [PATCH 42/98] chore: Add Author - Hiro --- docs/blog/authors.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index f30d4610d..c0ed02fae 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -3,4 +3,11 @@ dan-jan: title: Co-Founder url: https://github.com/dan-jan image_url: https://avatars.githubusercontent.com/u/101145494?v=4 - email: daniel@jan.ai \ No newline at end of file + email: daniel@jan.ai + +hiro-v: + name: Hiro Vuong + title: MLE + url: https://github.com/hiro-v + image_url: https://avatars.githubusercontent.com/u/22463238?v=4 + email: hiro@jan.ai \ No newline at end of file From 9dae95a222daa95b369fa3a1e3409127c92c9f62 Mon Sep 17 00:00:00 2001 From: Ashley Date: Wed, 7 Feb 2024 12:17:57 +0700 Subject: [PATCH 43/98] add author --- docs/blog/authors.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index f30d4610d..1a62bd7d7 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -3,4 +3,11 @@ dan-jan: title: Co-Founder url: https://github.com/dan-jan image_url: https://avatars.githubusercontent.com/u/101145494?v=4 - email: daniel@jan.ai \ No newline at end of file + email: daniel@jan.ai + +ashley-jan: + name: Ashley Tran + title: Product Designer + url: https://github.com/imtuyethan + image_url: https://avatars.githubusercontent.com/u/89722390?v=4 + email: ashley@jan.ai \ No newline at end of file From 893d73596ba5d13fa0d08e8991a93f323b1363e1 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 7 Feb 2024 13:21:02 +0700 Subject: [PATCH 44/98] chore: add author james Signed-off-by: James --- docs/blog/authors.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index f30d4610d..3bd5f9472 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -1,6 +1,13 @@ dan-jan: name: Daniel Onggunhao title: Co-Founder - url: https://github.com/dan-jan + url: https://github.com/dan-jan image_url: https://avatars.githubusercontent.com/u/101145494?v=4 - email: daniel@jan.ai \ No newline at end of file + email: daniel@jan.ai + +namchuai: + name: Nam Nguyen + title: Developer + url: https://github.com/namchuai + image_url: https://avatars.githubusercontent.com/u/10397206?v=4 + email: james@jan.ai From 2fc8f22c0039f6c1c042a4cddffddc172f5a8c1b Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:57:21 +0700 Subject: [PATCH 45/98] Update authors.yml --- docs/blog/authors.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index d15982e8c..044708811 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -25,4 +25,10 @@ ashley-jan: url: https://github.com/imtuyethan image_url: https://avatars.githubusercontent.com/u/89722390?v=4 email: ashley@jan.ai - + +hientominh: + name: Hien To + title: DevOps Engineer + url: https://github.com/hientominh + image_url: https://avatars.githubusercontent.com/u/37921427?v=4 + email: hien@jan.ai From fe29265ed6cd2dc08d37eef402b8f64ba5674076 Mon Sep 17 00:00:00 2001 From: Van Pham <64197333+Van-QA@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:02:25 +0700 Subject: [PATCH 46/98] Update authors.yml for Van Pham --- docs/blog/authors.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index d15982e8c..6ac49607a 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -25,4 +25,11 @@ ashley-jan: url: https://github.com/imtuyethan image_url: https://avatars.githubusercontent.com/u/89722390?v=4 email: ashley@jan.ai + +Van-QA: + name: Van Pham + title: QA & Release Manager + url: https://github.com/Van-QA + image_url: https://avatars.githubusercontent.com/u/64197333?v=4 + email: van@jan.ai From 60d4fd1f040b0085a5c4b5bae48e687a0c1398c8 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 7 Feb 2024 15:52:29 +0700 Subject: [PATCH 47/98] Update authors.yml Louis --- docs/blog/authors.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index 6ac49607a..9c9c02e2f 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -33,3 +33,9 @@ Van-QA: image_url: https://avatars.githubusercontent.com/u/64197333?v=4 email: van@jan.ai +louis-jan: + name: Louis Le + title: Software Engineer + url: https://github.com/louis-jan + image_url: https://avatars.githubusercontent.com/u/133622055?v=4 + email: louis@jan.ai From 41888650a8904cb8f26d747d53a527113016716c Mon Sep 17 00:00:00 2001 From: Hoang Ha <64120343+hahuyhoang411@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:11:43 +0700 Subject: [PATCH 48/98] Update authors.yml Rex --- docs/blog/authors.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index 6ac49607a..fe344cacb 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -32,4 +32,11 @@ Van-QA: url: https://github.com/Van-QA image_url: https://avatars.githubusercontent.com/u/64197333?v=4 email: van@jan.ai + +hahuyhoang411: + name: Rex Ha + title: LLM Researcher & Content writer + url: https://github.com/hahuyhoang411 + image_url: https://avatars.githubusercontent.com/u/64120343?v=4 + email: rex@jan.ai From 5890ade451da894eb4b778cf7491eebb99f69ae1 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 7 Feb 2024 17:54:35 +0700 Subject: [PATCH 49/98] chore: server download progress + S3 (#1925) * fix: reduce the number of api call Signed-off-by: James * fix: download progress Signed-off-by: James * chore: save blob * fix: server boot up * fix: download state not updating Signed-off-by: James * fix: copy assets * Add Dockerfile CPU for Jan Server and Jan Web * Add Dockerfile GPU for Jan Server and Jan Web * feat: S3 adapter * Update check find count from ./pre-install and correct copy:asserts command * server add bundleDependencies @janhq/core * server add bundleDependencies @janhq/core * fix: update success/failed download state (#1945) * fix: update success/failed download state Signed-off-by: James * fix: download model progress and state handling for both Desktop and Web --------- Signed-off-by: James Co-authored-by: James Co-authored-by: Louis * chore: refactor * fix: load models empty first time open * Add Docker compose * fix: assistants onUpdate --------- Signed-off-by: James Co-authored-by: James Co-authored-by: Hien To Co-authored-by: NamH --- Dockerfile | 67 +++++---- Dockerfile.gpu | 65 +++++++++ Makefile | 4 +- README.md | 25 ++++ core/package.json | 3 +- core/src/api/index.ts | 1 + core/src/node/api/routes/download.ts | 64 ++++++++- core/src/node/api/routes/fileManager.ts | 27 +++- core/src/node/api/routes/fs.ts | 13 +- core/src/node/api/routes/v1.ts | 3 + core/src/node/download.ts | 11 +- core/src/node/extension/index.ts | 10 +- core/src/types/assistant/assistantEvent.ts | 8 ++ core/src/types/assistant/index.ts | 1 + core/src/types/file/index.ts | 23 +++ core/src/types/model/modelEvent.ts | 2 + docker-compose.yml | 110 +++++++++++++++ electron/handlers/download.ts | 29 ++-- electron/handlers/fileManager.ts | 1 + electron/handlers/fs.ts | 3 +- electron/package.json | 13 +- extensions/assistant-extension/package.json | 6 +- extensions/assistant-extension/src/index.ts | 25 ++-- .../conversational-extension/package.json | 2 +- .../inference-nitro-extension/package.json | 6 +- .../inference-openai-extension/package.json | 2 +- .../package.json | 2 +- extensions/model-extension/package.json | 2 +- .../model-extension/src/@types/global.d.ts | 18 ++- .../model-extension/src/helpers/path.ts | 11 ++ extensions/model-extension/src/index.ts | 93 ++++++++++-- extensions/model-extension/tsconfig.json | 4 +- extensions/model-extension/webpack.config.js | 2 +- extensions/monitoring-extension/package.json | 2 +- package.json | 13 +- pre-install/.gitkeep | 0 server/helpers/setup.ts | 47 +++++++ server/index.ts | 6 +- server/main.ts | 10 +- server/middleware/s3.ts | 70 ++++++++++ server/nodemon.json | 5 - server/package.json | 13 +- server/tsconfig.json | 2 +- .../BottomBar/DownloadingState/index.tsx | 80 +++++------ web/containers/Layout/BottomBar/index.tsx | 7 +- web/containers/ModalCancelDownload/index.tsx | 13 +- web/containers/Providers/EventListener.tsx | 128 ++++++----------- web/helpers/atoms/Model.atom.ts | 23 +-- web/hooks/useAssistants.ts | 29 ++-- web/hooks/useDownloadModel.ts | 77 +++++----- web/hooks/useDownloadState.ts | 132 +++++++----------- web/hooks/useModels.ts | 33 +++-- web/hooks/useSetActiveThread.ts | 3 - web/next.config.js | 4 +- web/screens/Chat/ChatBody/index.tsx | 2 - web/screens/Chat/ErrorMessage/index.tsx | 1 - .../ExploreModelItemHeader/index.tsx | 3 +- .../ExploreModels/ModelVersionItem/index.tsx | 84 ----------- .../ExploreModels/ModelVersionList/index.tsx | 25 ---- web/services/restService.ts | 4 + web/types/downloadState.d.ts | 3 +- 61 files changed, 957 insertions(+), 518 deletions(-) create mode 100644 Dockerfile.gpu create mode 100644 core/src/types/assistant/assistantEvent.ts create mode 100644 docker-compose.yml create mode 100644 extensions/model-extension/src/helpers/path.ts create mode 100644 pre-install/.gitkeep create mode 100644 server/helpers/setup.ts create mode 100644 server/middleware/s3.ts delete mode 100644 server/nodemon.json delete mode 100644 web/screens/ExploreModels/ModelVersionItem/index.tsx delete mode 100644 web/screens/ExploreModels/ModelVersionList/index.tsx diff --git a/Dockerfile b/Dockerfile index 949a92673..82c657604 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,58 @@ -FROM node:20-bullseye AS base +FROM node:20-bookworm AS base # 1. Install dependencies only when needed -FROM base AS deps +FROM base AS builder + +# Install g++ 11 +RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel && rm -rf /var/lib/apt/lists/* + WORKDIR /app # Install dependencies based on the preferred package manager -COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ -RUN yarn install +COPY . ./ + +RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \ + jq --arg nitroVersion $NITRO_VERSION '(.scripts."downloadnitro:linux" | gsub("\\${NITRO_VERSION}"; $nitroVersion)) | gsub("\r"; "")' extensions/inference-nitro-extension/package.json > /tmp/newcommand.txt && export NEW_COMMAND=$(sed 's/^"//;s/"$//' /tmp/newcommand.txt) && jq --arg newCommand "$NEW_COMMAND" '.scripts."downloadnitro:linux" = $newCommand' extensions/inference-nitro-extension/package.json > /tmp/package.json && mv /tmp/package.json extensions/inference-nitro-extension/package.json +RUN make install-and-build +RUN yarn workspace jan-web install + +RUN export NODE_ENV=production && yarn workspace jan-web build # # 2. Rebuild the source code only when needed -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . -# This will do the trick, use the corresponding env file for each environment. -RUN yarn workspace server install -RUN yarn server:prod - -# 3. Production image, copy all the files and run next FROM base AS runner + +# Install g++ 11 +RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel && rm -rf /var/lib/apt/lists/* + WORKDIR /app -ENV NODE_ENV=production +# Copy the package.json and yarn.lock of root yarn space to leverage Docker cache +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/node_modules ./node_modules/ +COPY --from=builder /app/yarn.lock ./yarn.lock -# RUN addgroup -g 1001 -S nodejs; -COPY --from=builder /app/server/build ./ +# Copy the package.json, yarn.lock, and build output of server yarn space to leverage Docker cache +COPY --from=builder /app/server ./server/ +COPY --from=builder /app/docs/openapi ./docs/openapi/ -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder /app/server/node_modules ./node_modules -COPY --from=builder /app/server/package.json ./package.json +# Copy pre-install dependencies +COPY --from=builder /app/pre-install ./pre-install/ -EXPOSE 4000 3928 +# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache +COPY --from=builder /app/web/out ./web/out/ +COPY --from=builder /app/web/.next ./web/.next/ +COPY --from=builder /app/web/package.json ./web/package.json +COPY --from=builder /app/web/yarn.lock ./web/yarn.lock +COPY --from=builder /app/models ./models/ -ENV PORT 4000 -ENV APPDATA /app/data +RUN npm install -g serve@latest -CMD ["node", "main.js"] \ No newline at end of file +EXPOSE 1337 3000 3928 + +ENV JAN_API_HOST 0.0.0.0 +ENV JAN_API_PORT 1337 + +CMD ["sh", "-c", "cd server && node build/main.js & cd web && npx serve out"] + +# docker build -t jan . +# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 jan diff --git a/Dockerfile.gpu b/Dockerfile.gpu new file mode 100644 index 000000000..7b00e91d5 --- /dev/null +++ b/Dockerfile.gpu @@ -0,0 +1,65 @@ +FROM nvidia/cuda:12.0.0-devel-ubuntu22.04 AS base + +# 1. Install dependencies only when needed +FROM base AS builder + +# Install g++ 11 +RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel curl gnupg && curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt install nodejs -y && rm -rf /var/lib/apt/lists/* + +RUN npm install -g yarn + +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY . ./ + +RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \ + jq --arg nitroVersion $NITRO_VERSION '(.scripts."downloadnitro:linux" | gsub("\\${NITRO_VERSION}"; $nitroVersion)) | gsub("\r"; "")' extensions/inference-nitro-extension/package.json > /tmp/newcommand.txt && export NEW_COMMAND=$(sed 's/^"//;s/"$//' /tmp/newcommand.txt) && jq --arg newCommand "$NEW_COMMAND" '.scripts."downloadnitro:linux" = $newCommand' extensions/inference-nitro-extension/package.json > /tmp/package.json && mv /tmp/package.json extensions/inference-nitro-extension/package.json +RUN make install-and-build +RUN yarn workspace jan-web install + +RUN export NODE_ENV=production && yarn workspace jan-web build + +# # 2. Rebuild the source code only when needed +FROM base AS runner + +# Install g++ 11 +RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel curl gnupg && curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get install nodejs -y && rm -rf /var/lib/apt/lists/* + +RUN npm install -g yarn + +WORKDIR /app + +# Copy the package.json and yarn.lock of root yarn space to leverage Docker cache +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/node_modules ./node_modules/ +COPY --from=builder /app/yarn.lock ./yarn.lock + +# Copy the package.json, yarn.lock, and build output of server yarn space to leverage Docker cache +COPY --from=builder /app/server ./server/ +COPY --from=builder /app/docs/openapi ./docs/openapi/ + +# Copy pre-install dependencies +COPY --from=builder /app/pre-install ./pre-install/ + +# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache +COPY --from=builder /app/web/out ./web/out/ +COPY --from=builder /app/web/.next ./web/.next/ +COPY --from=builder /app/web/package.json ./web/package.json +COPY --from=builder /app/web/yarn.lock ./web/yarn.lock +COPY --from=builder /app/models ./models/ + +RUN npm install -g serve@latest + +EXPOSE 1337 3000 3928 + +ENV LD_LIBRARY_PATH=/usr/local/cuda-12.0/targets/x86_64-linux/lib:/usr/local/cuda-12.0/compat${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} + +ENV JAN_API_HOST 0.0.0.0 +ENV JAN_API_PORT 1337 + +CMD ["sh", "-c", "cd server && node build/main.js & cd web && npx serve out"] + +# pre-requisites: nvidia-docker +# docker build -t jan-gpu . -f Dockerfile.gpu +# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 --gpus all jan-gpu diff --git a/Makefile b/Makefile index 905a68321..ffb1abee2 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,9 @@ endif check-file-counts: install-and-build ifeq ($(OS),Windows_NT) - powershell -Command "if ((Get-ChildItem -Path electron/pre-install -Filter *.tgz | Measure-Object | Select-Object -ExpandProperty Count) -ne (Get-ChildItem -Path extensions -Directory | Measure-Object | Select-Object -ExpandProperty Count)) { Write-Host 'Number of .tgz files in electron/pre-install does not match the number of subdirectories in extension'; exit 1 } else { Write-Host 'Extension build successful' }" + powershell -Command "if ((Get-ChildItem -Path pre-install -Filter *.tgz | Measure-Object | Select-Object -ExpandProperty Count) -ne (Get-ChildItem -Path extensions -Directory | Measure-Object | Select-Object -ExpandProperty Count)) { Write-Host 'Number of .tgz files in pre-install does not match the number of subdirectories in extension'; exit 1 } else { Write-Host 'Extension build successful' }" else - @tgz_count=$$(find electron/pre-install -type f -name "*.tgz" | wc -l); dir_count=$$(find extensions -mindepth 1 -maxdepth 1 -type d | wc -l); if [ $$tgz_count -ne $$dir_count ]; then echo "Number of .tgz files in electron/pre-install ($$tgz_count) does not match the number of subdirectories in extension ($$dir_count)"; exit 1; else echo "Extension build successful"; fi + @tgz_count=$$(find pre-install -type f -name "*.tgz" | wc -l); dir_count=$$(find extensions -mindepth 1 -maxdepth 1 -type d | wc -l); if [ $$tgz_count -ne $$dir_count ]; then echo "Number of .tgz files in pre-install ($$tgz_count) does not match the number of subdirectories in extension ($$dir_count)"; exit 1; else echo "Extension build successful"; fi endif dev: check-file-counts diff --git a/README.md b/README.md index e1f74ef23..14437498b 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,31 @@ make build This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder. +### Docker mode + +- Supported OS: Linux, WSL2 Docker +- Pre-requisites: + - `docker` and `docker compose`, follow instruction [here](https://docs.docker.com/engine/install/ubuntu/) + + ```bash + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh ./get-docker.sh --dry-run + ``` + + - `nvidia docker`, follow instruction [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) (If you want to run with GPU mode) + +- Run Jan in Docker mode + + ```bash + # CPU mode + docker compose --profile cpu up + + # GPU mode + docker compose --profile gpu up + ``` + + This will start the web server and you can access Jan at `http://localhost:3000`. + ## Acknowledgements Jan builds on top of other open-source projects: diff --git a/core/package.json b/core/package.json index 437e6d0a6..c3abe2d56 100644 --- a/core/package.json +++ b/core/package.json @@ -57,6 +57,7 @@ "rollup-plugin-typescript2": "^0.36.0", "ts-jest": "^26.1.1", "tslib": "^2.6.2", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "rimraf": "^3.0.2" } } diff --git a/core/src/api/index.ts b/core/src/api/index.ts index 0d7cc51f7..f4ec3cd7e 100644 --- a/core/src/api/index.ts +++ b/core/src/api/index.ts @@ -30,6 +30,7 @@ export enum DownloadRoute { downloadFile = 'downloadFile', pauseDownload = 'pauseDownload', resumeDownload = 'resumeDownload', + getDownloadProgress = 'getDownloadProgress', } export enum DownloadEvent { diff --git a/core/src/node/api/routes/download.ts b/core/src/node/api/routes/download.ts index ab8c0bd37..7fb05daee 100644 --- a/core/src/node/api/routes/download.ts +++ b/core/src/node/api/routes/download.ts @@ -5,8 +5,27 @@ import { HttpServer } from '../HttpServer' import { createWriteStream } from 'fs' import { getJanDataFolderPath } from '../../utils' import { normalizeFilePath } from '../../path' +import { DownloadState } from '../../../types' export const downloadRouter = async (app: HttpServer) => { + app.get(`/${DownloadRoute.getDownloadProgress}/:modelId`, async (req, res) => { + const modelId = req.params.modelId + + console.debug(`Getting download progress for model ${modelId}`) + console.debug( + `All Download progress: ${JSON.stringify(DownloadManager.instance.downloadProgressMap)}` + ) + + // check if null DownloadManager.instance.downloadProgressMap + if (!DownloadManager.instance.downloadProgressMap[modelId]) { + return res.status(404).send({ + message: 'Download progress not found', + }) + } else { + return res.status(200).send(DownloadManager.instance.downloadProgressMap[modelId]) + } + }) + app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => { const strictSSL = !(req.query.ignoreSSL === 'true') const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined @@ -19,7 +38,10 @@ export const downloadRouter = async (app: HttpServer) => { }) const localPath = normalizedArgs[1] - const fileName = localPath.split('/').pop() ?? '' + const array = localPath.split('/') + const fileName = array.pop() ?? '' + const modelId = array.pop() ?? '' + console.debug('downloadFile', normalizedArgs, fileName, modelId) const request = require('request') const progress = require('request-progress') @@ -27,17 +49,44 @@ export const downloadRouter = async (app: HttpServer) => { const rq = request({ url: normalizedArgs[0], strictSSL, proxy }) progress(rq, {}) .on('progress', function (state: any) { - console.log('download onProgress', state) + const downloadProps: DownloadState = { + ...state, + modelId, + fileName, + downloadState: 'downloading', + } + console.debug(`Download ${modelId} onProgress`, downloadProps) + DownloadManager.instance.downloadProgressMap[modelId] = downloadProps }) .on('error', function (err: Error) { - console.log('download onError', err) + console.debug(`Download ${modelId} onError`, err.message) + + const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId] + if (currentDownloadState) { + DownloadManager.instance.downloadProgressMap[modelId] = { + ...currentDownloadState, + downloadState: 'error', + } + } }) .on('end', function () { - console.log('download onEnd') + console.debug(`Download ${modelId} onEnd`) + + const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId] + if (currentDownloadState) { + if (currentDownloadState.downloadState === 'downloading') { + // if the previous state is downloading, then set the state to end (success) + DownloadManager.instance.downloadProgressMap[modelId] = { + ...currentDownloadState, + downloadState: 'end', + } + } + } }) .pipe(createWriteStream(normalizedArgs[1])) - DownloadManager.instance.setRequest(fileName, rq) + DownloadManager.instance.setRequest(localPath, rq) + res.status(200).send({ message: 'Download started' }) }) app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => { @@ -54,5 +103,10 @@ export const downloadRouter = async (app: HttpServer) => { const rq = DownloadManager.instance.networkRequests[fileName] DownloadManager.instance.networkRequests[fileName] = undefined rq?.abort() + if (rq) { + res.status(200).send({ message: 'Download aborted' }) + } else { + res.status(404).send({ message: 'Download not found' }) + } }) } diff --git a/core/src/node/api/routes/fileManager.ts b/core/src/node/api/routes/fileManager.ts index 66056444e..b4c73dda1 100644 --- a/core/src/node/api/routes/fileManager.ts +++ b/core/src/node/api/routes/fileManager.ts @@ -1,14 +1,29 @@ import { FileManagerRoute } from '../../../api' import { HttpServer } from '../../index' +import { join } from 'path' -export const fsRouter = async (app: HttpServer) => { - app.post(`/app/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {}) +export const fileManagerRouter = async (app: HttpServer) => { + app.post(`/fs/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => { + const reflect = require('@alumna/reflect') + const args = JSON.parse(request.body) + return reflect({ + src: args[0], + dest: args[1], + recursive: true, + delete: false, + overwrite: true, + errorOnExist: false, + }) + }) - app.post(`/app/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) => {}) + app.post(`/fs/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) => + global.core.appPath() + ) - app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {}) + app.post(`/fs/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => + join(global.core.appPath(), '../../..') + ) app.post(`/app/${FileManagerRoute.getUserHomePath}`, async (request: any, reply: any) => {}) - - app.post(`/app/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {}) + app.post(`/fs/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {}) } diff --git a/core/src/node/api/routes/fs.ts b/core/src/node/api/routes/fs.ts index c5404ccce..9535418a0 100644 --- a/core/src/node/api/routes/fs.ts +++ b/core/src/node/api/routes/fs.ts @@ -1,8 +1,9 @@ -import { FileSystemRoute } from '../../../api' +import { FileManagerRoute, FileSystemRoute } from '../../../api' import { join } from 'path' import { HttpServer } from '../HttpServer' import { getJanDataFolderPath } from '../../utils' import { normalizeFilePath } from '../../path' +import { writeFileSync } from 'fs' export const fsRouter = async (app: HttpServer) => { const moduleName = 'fs' @@ -26,4 +27,14 @@ export const fsRouter = async (app: HttpServer) => { } }) }) + app.post(`/${FileManagerRoute.writeBlob}`, async (request: any, reply: any) => { + try { + const args = JSON.parse(request.body) as any[] + console.log('writeBlob:', args[0]) + const dataBuffer = Buffer.from(args[1], 'base64') + writeFileSync(args[0], dataBuffer) + } catch (err) { + console.error(`writeFile ${request.body} result: ${err}`) + } + }) } diff --git a/core/src/node/api/routes/v1.ts b/core/src/node/api/routes/v1.ts index a2a48cd8b..301c41ac0 100644 --- a/core/src/node/api/routes/v1.ts +++ b/core/src/node/api/routes/v1.ts @@ -4,6 +4,7 @@ import { threadRouter } from './thread' import { fsRouter } from './fs' import { extensionRouter } from './extension' import { downloadRouter } from './download' +import { fileManagerRouter } from './fileManager' export const v1Router = async (app: HttpServer) => { // MARK: External Routes @@ -16,6 +17,8 @@ export const v1Router = async (app: HttpServer) => { app.register(fsRouter, { prefix: '/fs', }) + app.register(fileManagerRouter) + app.register(extensionRouter, { prefix: '/extension', }) diff --git a/core/src/node/download.ts b/core/src/node/download.ts index 6d15fc344..b3f284440 100644 --- a/core/src/node/download.ts +++ b/core/src/node/download.ts @@ -1,15 +1,18 @@ +import { DownloadState } from '../types' /** * Manages file downloads and network requests. */ export class DownloadManager { - public networkRequests: Record = {}; + public networkRequests: Record = {} - public static instance: DownloadManager = new DownloadManager(); + public static instance: DownloadManager = new DownloadManager() + + public downloadProgressMap: Record = {} constructor() { if (DownloadManager.instance) { - return DownloadManager.instance; + return DownloadManager.instance } } /** @@ -18,6 +21,6 @@ export class DownloadManager { * @param {Request | undefined} request - The network request to set, or undefined to clear the request. */ setRequest(fileName: string, request: any | undefined) { - this.networkRequests[fileName] = request; + this.networkRequests[fileName] = request } } diff --git a/core/src/node/extension/index.ts b/core/src/node/extension/index.ts index ed8544773..994fc97f2 100644 --- a/core/src/node/extension/index.ts +++ b/core/src/node/extension/index.ts @@ -41,8 +41,8 @@ async function registerExtensionProtocol() { console.error('Electron is not available') } const extensionPath = ExtensionManager.instance.getExtensionsPath() - if (electron) { - return electron.protocol.registerFileProtocol('extension', (request: any, callback: any) => { + if (electron && electron.protocol) { + return electron.protocol?.registerFileProtocol('extension', (request: any, callback: any) => { const entry = request.url.substr('extension://'.length - 1) const url = normalize(extensionPath + entry) @@ -69,7 +69,7 @@ export function useExtensions(extensionsPath: string) { // Read extension list from extensions folder const extensions = JSON.parse( - readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8'), + readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8') ) try { // Create and store a Extension instance for each extension in list @@ -82,7 +82,7 @@ export function useExtensions(extensionsPath: string) { throw new Error( 'Could not successfully rebuild list of installed extensions.\n' + error + - '\nPlease check the extensions.json file in the extensions folder.', + '\nPlease check the extensions.json file in the extensions folder.' ) } @@ -122,7 +122,7 @@ function loadExtension(ext: any) { export function getStore() { if (!ExtensionManager.instance.getExtensionsFile()) { throw new Error( - 'The extension path has not yet been set up. Please run useExtensions before accessing the store', + 'The extension path has not yet been set up. Please run useExtensions before accessing the store' ) } diff --git a/core/src/types/assistant/assistantEvent.ts b/core/src/types/assistant/assistantEvent.ts new file mode 100644 index 000000000..f8f3e6ad0 --- /dev/null +++ b/core/src/types/assistant/assistantEvent.ts @@ -0,0 +1,8 @@ +/** + * The `EventName` enumeration contains the names of all the available events in the Jan platform. + */ +export enum AssistantEvent { + /** The `OnAssistantsUpdate` event is emitted when the assistant list is updated. */ + OnAssistantsUpdate = 'OnAssistantsUpdate', + } + \ No newline at end of file diff --git a/core/src/types/assistant/index.ts b/core/src/types/assistant/index.ts index 83ea73f85..e18589551 100644 --- a/core/src/types/assistant/index.ts +++ b/core/src/types/assistant/index.ts @@ -1,2 +1,3 @@ export * from './assistantEntity' +export * from './assistantEvent' export * from './assistantInterface' diff --git a/core/src/types/file/index.ts b/core/src/types/file/index.ts index 6526cfc6d..57d687d2f 100644 --- a/core/src/types/file/index.ts +++ b/core/src/types/file/index.ts @@ -2,3 +2,26 @@ export type FileStat = { isDirectory: boolean size: number } + +export type DownloadState = { + modelId: string + filename: string + time: DownloadTime + speed: number + percent: number + + size: DownloadSize + children?: DownloadState[] + error?: string + downloadState: 'downloading' | 'error' | 'end' +} + +type DownloadTime = { + elapsed: number + remaining: number +} + +type DownloadSize = { + total: number + transferred: number +} diff --git a/core/src/types/model/modelEvent.ts b/core/src/types/model/modelEvent.ts index 978a48724..443f3a34f 100644 --- a/core/src/types/model/modelEvent.ts +++ b/core/src/types/model/modelEvent.ts @@ -12,4 +12,6 @@ export enum ModelEvent { OnModelStop = 'OnModelStop', /** The `OnModelStopped` event is emitted when a model stopped ok. */ OnModelStopped = 'OnModelStopped', + /** The `OnModelUpdate` event is emitted when the model list is updated. */ + OnModelsUpdate = 'OnModelsUpdate', } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..fd0f44096 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,110 @@ +version: '3.7' + +services: + minio: + image: minio/minio + volumes: + - minio_data:/data + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY + MINIO_ROOT_PASSWORD: minioadmin # This acts as AWS_SECRET_ACCESS_KEY + command: server --console-address ":9001" /data + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + networks: + vpcbr: + ipv4_address: 10.5.0.2 + + createbuckets: + image: minio/mc + depends_on: + - minio + entrypoint: > + /bin/sh -c " + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/mybucket; + /usr/bin/mc policy set public myminio/mybucket; + exit 0; + " + networks: + vpcbr: + + + app_cpu: + image: jan:latest + volumes: + - app_data:/app/server/build/jan + build: + context: . + dockerfile: Dockerfile + environment: + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin + S3_BUCKET_NAME: mybucket + AWS_ENDPOINT: http://10.5.0.2:9000 + AWS_REGION: us-east-1 + restart: always + profiles: + - cpu + ports: + - "3000:3000" + - "1337:1337" + - "3928:3928" + networks: + vpcbr: + ipv4_address: 10.5.0.3 + + + app_gpu: + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + image: jan-gpu:latest + volumes: + - app_data:/app/server/build/jan + build: + context: . + dockerfile: Dockerfile.gpu + restart: always + environment: + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin + S3_BUCKET_NAME: mybucket + AWS_ENDPOINT: http://10.5.0.2:9000 + AWS_REGION: us-east-1 + + profiles: + - gpu + ports: + - "3000:3000" + - "1337:1337" + - "3928:3928" + networks: + vpcbr: + ipv4_address: 10.5.0.4 + +volumes: + minio_data: + app_data: + +networks: + vpcbr: + driver: bridge + ipam: + config: + - subnet: 10.5.0.0/16 + gateway: 10.5.0.1 + +# docker compose --profile cpu up +# docker compose --profile gpu up diff --git a/electron/handlers/download.ts b/electron/handlers/download.ts index f63e56f6b..5f1d8371e 100644 --- a/electron/handlers/download.ts +++ b/electron/handlers/download.ts @@ -5,7 +5,11 @@ import request from 'request' import { createWriteStream, renameSync } from 'fs' import { DownloadEvent, DownloadRoute } from '@janhq/core' const progress = require('request-progress') -import { DownloadManager, getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node' +import { + DownloadManager, + getJanDataFolderPath, + normalizeFilePath, +} from '@janhq/core/node' export function handleDownloaderIPCs() { /** @@ -56,20 +60,23 @@ export function handleDownloaderIPCs() { */ ipcMain.handle( DownloadRoute.downloadFile, - async (_event, url, fileName, network) => { + async (_event, url, localPath, network) => { const strictSSL = !network?.ignoreSSL const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined - - if (typeof fileName === 'string') { - fileName = normalizeFilePath(fileName) + if (typeof localPath === 'string') { + localPath = normalizeFilePath(localPath) } - const destination = resolve(getJanDataFolderPath(), fileName) + const array = localPath.split('/') + const fileName = array.pop() ?? '' + const modelId = array.pop() ?? '' + + const destination = resolve(getJanDataFolderPath(), localPath) const rq = request({ url, strictSSL, proxy }) // Put request to download manager instance - DownloadManager.instance.setRequest(fileName, rq) + DownloadManager.instance.setRequest(localPath, rq) // Downloading file to a temp file first const downloadingTempFile = `${destination}.download` @@ -81,6 +88,7 @@ export function handleDownloaderIPCs() { { ...state, fileName, + modelId, } ) }) @@ -90,11 +98,12 @@ export function handleDownloaderIPCs() { { fileName, err, + modelId, } ) }) .on('end', function () { - if (DownloadManager.instance.networkRequests[fileName]) { + if (DownloadManager.instance.networkRequests[localPath]) { // Finished downloading, rename temp file to actual file renameSync(downloadingTempFile, destination) @@ -102,14 +111,16 @@ export function handleDownloaderIPCs() { DownloadEvent.onFileDownloadSuccess, { fileName, + modelId, } ) - DownloadManager.instance.setRequest(fileName, undefined) + DownloadManager.instance.setRequest(localPath, undefined) } else { WindowManager?.instance.currentWindow?.webContents.send( DownloadEvent.onFileDownloadError, { fileName, + modelId, err: { message: 'aborted' }, } ) diff --git a/electron/handlers/fileManager.ts b/electron/handlers/fileManager.ts index e328cb53b..15c371d34 100644 --- a/electron/handlers/fileManager.ts +++ b/electron/handlers/fileManager.ts @@ -38,6 +38,7 @@ export function handleFileMangerIPCs() { getResourcePath() ) + // Handles the 'getUserHomePath' IPC event. This event is triggered to get the user home path. ipcMain.handle(FileManagerRoute.getUserHomePath, async (_event) => app.getPath('home') ) diff --git a/electron/handlers/fs.ts b/electron/handlers/fs.ts index 34026b940..8ac575cb2 100644 --- a/electron/handlers/fs.ts +++ b/electron/handlers/fs.ts @@ -1,8 +1,7 @@ import { ipcMain } from 'electron' import { getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node' -import fs from 'fs' -import { FileManagerRoute, FileSystemRoute } from '@janhq/core' +import { FileSystemRoute } from '@janhq/core' import { join } from 'path' /** * Handles file system operations. diff --git a/electron/package.json b/electron/package.json index 08f15b262..229979b41 100644 --- a/electron/package.json +++ b/electron/package.json @@ -57,17 +57,18 @@ "scripts": { "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", "test:e2e": "playwright test --workers=1", - "dev": "tsc -p . && electron .", - "build": "run-script-os", - "build:test": "run-script-os", + "copy:assets": "rimraf --glob \"./pre-install/*.tgz\" && cpx \"../pre-install/*.tgz\" \"./pre-install\"", + "dev": "yarn copy:assets && tsc -p . && electron .", + "build": "yarn copy:assets && run-script-os", + "build:test": "yarn copy:assets && run-script-os", "build:test:darwin": "tsc -p . && electron-builder -p never -m --dir", "build:test:win32": "tsc -p . && electron-builder -p never -w --dir", "build:test:linux": "tsc -p . && electron-builder -p never -l --dir", - "build:darwin": "tsc -p . && electron-builder -p never -m", + "build:darwin": "tsc -p . && electron-builder -p never -m --x64 --arm64", "build:win32": "tsc -p . && electron-builder -p never -w", "build:linux": "tsc -p . && electron-builder -p never -l deb -l AppImage", - "build:publish": "run-script-os", - "build:publish:darwin": "tsc -p . && electron-builder -p always -m", + "build:publish": "yarn copy:assets && run-script-os", + "build:publish:darwin": "tsc -p . && electron-builder -p always -m --x64 --arm64", "build:publish:win32": "tsc -p . && electron-builder -p always -w", "build:publish:linux": "tsc -p . && electron-builder -p always -l deb -l AppImage" }, diff --git a/extensions/assistant-extension/package.json b/extensions/assistant-extension/package.json index 84bcdf47e..5f45ecabe 100644 --- a/extensions/assistant-extension/package.json +++ b/extensions/assistant-extension/package.json @@ -8,9 +8,9 @@ "license": "AGPL-3.0", "scripts": { "build": "tsc --module commonjs && rollup -c rollup.config.ts", - "build:publish:linux": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install", - "build:publish:darwin": "rimraf *.tgz --glob && npm run build && ../../.github/scripts/auto-sign.sh && npm pack && cpx *.tgz ../../electron/pre-install", - "build:publish:win32": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install", + "build:publish:linux": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install", + "build:publish:darwin": "rimraf *.tgz --glob && npm run build && ../../.github/scripts/auto-sign.sh && npm pack && cpx *.tgz ../../pre-install", + "build:publish:win32": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install", "build:publish": "run-script-os" }, "devDependencies": { diff --git a/extensions/assistant-extension/src/index.ts b/extensions/assistant-extension/src/index.ts index 6495ea786..8bc8cafdc 100644 --- a/extensions/assistant-extension/src/index.ts +++ b/extensions/assistant-extension/src/index.ts @@ -9,6 +9,7 @@ import { joinPath, executeOnMain, AssistantExtension, + AssistantEvent, } from "@janhq/core"; export default class JanAssistantExtension extends AssistantExtension { @@ -21,7 +22,7 @@ export default class JanAssistantExtension extends AssistantExtension { async onLoad() { // making the assistant directory const assistantDirExist = await fs.existsSync( - JanAssistantExtension._homeDir, + JanAssistantExtension._homeDir ); if ( localStorage.getItem(`${EXTENSION_NAME}-version`) !== VERSION || @@ -31,14 +32,16 @@ export default class JanAssistantExtension extends AssistantExtension { await fs.mkdirSync(JanAssistantExtension._homeDir); // Write assistant metadata - this.createJanAssistant(); + await this.createJanAssistant(); // Finished migration localStorage.setItem(`${EXTENSION_NAME}-version`, VERSION); + // Update the assistant list + events.emit(AssistantEvent.OnAssistantsUpdate, {}); } // Events subscription events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => - JanAssistantExtension.handleMessageRequest(data, this), + JanAssistantExtension.handleMessageRequest(data, this) ); events.on(InferenceEvent.OnInferenceStopped, () => { @@ -53,7 +56,7 @@ export default class JanAssistantExtension extends AssistantExtension { private static async handleMessageRequest( data: MessageRequest, - instance: JanAssistantExtension, + instance: JanAssistantExtension ) { instance.isCancelled = false; instance.controller = new AbortController(); @@ -80,7 +83,7 @@ export default class JanAssistantExtension extends AssistantExtension { NODE, "toolRetrievalIngestNewDocument", docFile, - data.model?.proxyEngine, + data.model?.proxyEngine ); } } @@ -96,7 +99,7 @@ export default class JanAssistantExtension extends AssistantExtension { NODE, "toolRetrievalUpdateTextSplitter", data.thread.assistants[0].tools[0]?.settings?.chunk_size ?? 4000, - data.thread.assistants[0].tools[0]?.settings?.chunk_overlap ?? 200, + data.thread.assistants[0].tools[0]?.settings?.chunk_overlap ?? 200 ); } @@ -110,7 +113,7 @@ export default class JanAssistantExtension extends AssistantExtension { const retrievalResult = await executeOnMain( NODE, "toolRetrievalQueryResult", - prompt, + prompt ); // Update the message content @@ -168,7 +171,7 @@ export default class JanAssistantExtension extends AssistantExtension { try { await fs.writeFileSync( assistantMetadataPath, - JSON.stringify(assistant, null, 2), + JSON.stringify(assistant, null, 2) ); } catch (err) { console.error(err); @@ -180,7 +183,7 @@ export default class JanAssistantExtension extends AssistantExtension { // get all the assistant metadata json const results: Assistant[] = []; const allFileName: string[] = await fs.readdirSync( - JanAssistantExtension._homeDir, + JanAssistantExtension._homeDir ); for (const fileName of allFileName) { const filePath = await joinPath([ @@ -190,7 +193,7 @@ export default class JanAssistantExtension extends AssistantExtension { if (filePath.includes(".DS_Store")) continue; const jsonFiles: string[] = (await fs.readdirSync(filePath)).filter( - (file: string) => file === "assistant.json", + (file: string) => file === "assistant.json" ); if (jsonFiles.length !== 1) { @@ -200,7 +203,7 @@ export default class JanAssistantExtension extends AssistantExtension { const content = await fs.readFileSync( await joinPath([filePath, jsonFiles[0]]), - "utf-8", + "utf-8" ); const assistant: Assistant = typeof content === "object" ? content : JSON.parse(content); diff --git a/extensions/conversational-extension/package.json b/extensions/conversational-extension/package.json index a60c12339..b84c75d3d 100644 --- a/extensions/conversational-extension/package.json +++ b/extensions/conversational-extension/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install" }, "exports": { ".": "./dist/index.js", diff --git a/extensions/inference-nitro-extension/package.json b/extensions/inference-nitro-extension/package.json index 8ad516ad9..cccfbefd0 100644 --- a/extensions/inference-nitro-extension/package.json +++ b/extensions/inference-nitro-extension/package.json @@ -12,9 +12,9 @@ "downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro", "downloadnitro:win32": "download.bat", "downloadnitro": "run-script-os", - "build:publish:darwin": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../electron/pre-install", - "build:publish:win32": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../electron/pre-install", - "build:publish:linux": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../electron/pre-install", + "build:publish:darwin": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", + "build:publish:win32": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", + "build:publish:linux": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", "build:publish": "run-script-os" }, "exports": { diff --git a/extensions/inference-openai-extension/package.json b/extensions/inference-openai-extension/package.json index 5fa0ce974..0ba6f18db 100644 --- a/extensions/inference-openai-extension/package.json +++ b/extensions/inference-openai-extension/package.json @@ -8,7 +8,7 @@ "license": "AGPL-3.0", "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install" }, "exports": { ".": "./dist/index.js", diff --git a/extensions/inference-triton-trtllm-extension/package.json b/extensions/inference-triton-trtllm-extension/package.json index 1d27f9f18..0f4c2de23 100644 --- a/extensions/inference-triton-trtllm-extension/package.json +++ b/extensions/inference-triton-trtllm-extension/package.json @@ -8,7 +8,7 @@ "license": "AGPL-3.0", "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install" }, "exports": { ".": "./dist/index.js", diff --git a/extensions/model-extension/package.json b/extensions/model-extension/package.json index 86f177d14..1af5d38cb 100644 --- a/extensions/model-extension/package.json +++ b/extensions/model-extension/package.json @@ -8,7 +8,7 @@ "license": "AGPL-3.0", "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install" }, "devDependencies": { "cpx": "^1.5.0", diff --git a/extensions/model-extension/src/@types/global.d.ts b/extensions/model-extension/src/@types/global.d.ts index e998455f2..7a9202a62 100644 --- a/extensions/model-extension/src/@types/global.d.ts +++ b/extensions/model-extension/src/@types/global.d.ts @@ -1,3 +1,15 @@ -declare const EXTENSION_NAME: string -declare const MODULE_PATH: string -declare const VERSION: stringå +export {} +declare global { + declare const EXTENSION_NAME: string + declare const MODULE_PATH: string + declare const VERSION: string + + interface Core { + api: APIFunctions + events: EventEmitter + } + interface Window { + core?: Core | undefined + electronAPI?: any | undefined + } +} diff --git a/extensions/model-extension/src/helpers/path.ts b/extensions/model-extension/src/helpers/path.ts new file mode 100644 index 000000000..cbb151aa6 --- /dev/null +++ b/extensions/model-extension/src/helpers/path.ts @@ -0,0 +1,11 @@ +/** + * try to retrieve the download file name from the source url + */ + +export function extractFileName(url: string, fileExtension: string): string { + const extractedFileName = url.split('/').pop() + const fileName = extractedFileName.toLowerCase().endsWith(fileExtension) + ? extractedFileName + : extractedFileName + fileExtension + return fileName +} diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index b9fa7731e..e26fd4929 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -8,7 +8,13 @@ import { ModelExtension, Model, getJanDataFolderPath, + events, + DownloadEvent, + DownloadRoute, + ModelEvent, } from '@janhq/core' +import { DownloadState } from '@janhq/core/.' +import { extractFileName } from './helpers/path' /** * A extension for models @@ -29,6 +35,8 @@ export default class JanModelExtension extends ModelExtension { */ async onLoad() { this.copyModelsToHomeDir() + // Handle Desktop Events + this.handleDesktopEvents() } /** @@ -61,6 +69,8 @@ export default class JanModelExtension extends ModelExtension { // Finished migration localStorage.setItem(`${EXTENSION_NAME}-version`, VERSION) + + events.emit(ModelEvent.OnModelsUpdate, {}) } catch (err) { console.error(err) } @@ -83,31 +93,66 @@ export default class JanModelExtension extends ModelExtension { if (model.sources.length > 1) { // path to model binaries for (const source of model.sources) { - let path = this.extractFileName(source.url) + let path = extractFileName( + source.url, + JanModelExtension._supportedModelFormat + ) if (source.filename) { path = await joinPath([modelDirPath, source.filename]) } downloadFile(source.url, path, network) } + // TODO: handle multiple binaries for web later } else { - const fileName = this.extractFileName(model.sources[0]?.url) + const fileName = extractFileName( + model.sources[0]?.url, + JanModelExtension._supportedModelFormat + ) const path = await joinPath([modelDirPath, fileName]) downloadFile(model.sources[0]?.url, path, network) + + if (window && window.core?.api && window.core.api.baseApiUrl) { + this.startPollingDownloadProgress(model.id) + } } } /** - * try to retrieve the download file name from the source url + * Specifically for Jan server. */ - private extractFileName(url: string): string { - const extractedFileName = url.split('/').pop() - const fileName = extractedFileName - .toLowerCase() - .endsWith(JanModelExtension._supportedModelFormat) - ? extractedFileName - : extractedFileName + JanModelExtension._supportedModelFormat - return fileName + private async startPollingDownloadProgress(modelId: string): Promise { + // wait for some seconds before polling + await new Promise((resolve) => setTimeout(resolve, 3000)) + + return new Promise((resolve) => { + const interval = setInterval(async () => { + fetch( + `${window.core.api.baseApiUrl}/v1/download/${DownloadRoute.getDownloadProgress}/${modelId}`, + { + method: 'GET', + headers: { contentType: 'application/json' }, + } + ).then(async (res) => { + const state: DownloadState = await res.json() + if (state.downloadState === 'end') { + events.emit(DownloadEvent.onFileDownloadSuccess, state) + clearInterval(interval) + resolve() + return + } + + if (state.downloadState === 'error') { + events.emit(DownloadEvent.onFileDownloadError, state) + clearInterval(interval) + resolve() + return + } + + events.emit(DownloadEvent.onFileDownloadUpdate, state) + }) + }, 1000) + }) } /** @@ -318,7 +363,7 @@ export default class JanModelExtension extends ModelExtension { return } - const defaultModel = await this.getDefaultModel() as Model + const defaultModel = (await this.getDefaultModel()) as Model if (!defaultModel) { console.error('Unable to find default model') return @@ -382,4 +427,28 @@ export default class JanModelExtension extends ModelExtension { async getConfiguredModels(): Promise { return this.getModelsMetadata() } + + handleDesktopEvents() { + if (window && window.electronAPI) { + window.electronAPI.onFileDownloadUpdate( + async (_event: string, state: any | undefined) => { + if (!state) return + state.downloadState = 'update' + events.emit(DownloadEvent.onFileDownloadUpdate, state) + } + ) + window.electronAPI.onFileDownloadError( + async (_event: string, state: any) => { + state.downloadState = 'error' + events.emit(DownloadEvent.onFileDownloadError, state) + } + ) + window.electronAPI.onFileDownloadSuccess( + async (_event: string, state: any) => { + state.downloadState = 'end' + events.emit(DownloadEvent.onFileDownloadSuccess, state) + } + ) + } + } } diff --git a/extensions/model-extension/tsconfig.json b/extensions/model-extension/tsconfig.json index addd8e127..c175d9437 100644 --- a/extensions/model-extension/tsconfig.json +++ b/extensions/model-extension/tsconfig.json @@ -8,7 +8,7 @@ "forceConsistentCasingInFileNames": true, "strict": false, "skipLibCheck": true, - "rootDir": "./src" + "rootDir": "./src", }, - "include": ["./src"] + "include": ["./src"], } diff --git a/extensions/model-extension/webpack.config.js b/extensions/model-extension/webpack.config.js index 347719f91..c67bf8dc0 100644 --- a/extensions/model-extension/webpack.config.js +++ b/extensions/model-extension/webpack.config.js @@ -19,7 +19,7 @@ module.exports = { new webpack.DefinePlugin({ EXTENSION_NAME: JSON.stringify(packageJson.name), MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`), - VERSION: JSON.stringify(packageJson.version), + VERSION: JSON.stringify(packageJson.version) }), ], output: { diff --git a/extensions/monitoring-extension/package.json b/extensions/monitoring-extension/package.json index 20d3c485f..538f6bdee 100644 --- a/extensions/monitoring-extension/package.json +++ b/extensions/monitoring-extension/package.json @@ -8,7 +8,7 @@ "license": "AGPL-3.0", "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../pre-install" }, "devDependencies": { "rimraf": "^3.0.2", diff --git a/package.json b/package.json index 4b8bc4af0..957934fda 100644 --- a/package.json +++ b/package.json @@ -21,22 +21,23 @@ "lint": "yarn workspace jan lint && yarn workspace jan-web lint", "test:unit": "yarn workspace @janhq/core test", "test": "yarn workspace jan test:e2e", - "copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"", + "copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"pre-install/*.tgz\" \"electron/pre-install/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"", "dev:electron": "yarn copy:assets && yarn workspace jan dev", "dev:web": "yarn workspace jan-web dev", - "dev:server": "yarn workspace @janhq/server dev", + "dev:server": "yarn copy:assets && yarn workspace @janhq/server dev", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", "test-local": "yarn lint && yarn build:test && yarn test", "dev:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit dev", "build:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build", - "build:server": "cd server && yarn install && yarn run build", + "build:server": "yarn copy:assets && cd server && yarn install && yarn run build", "build:core": "cd core && yarn install && yarn run build", "build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"", "build:electron": "yarn copy:assets && yarn workspace jan build", "build:electron:test": "yarn workspace jan build:test", - "build:extensions:windows": "rimraf ./electron/pre-install/*.tgz && powershell -command \"$jobs = Get-ChildItem -Path './extensions' -Directory | ForEach-Object { Start-Job -Name ($_.Name) -ScriptBlock { param($_dir); try { Set-Location $_dir; npm install; npm run build:publish; Write-Output 'Build successful in ' + $_dir } catch { Write-Error 'Error in ' + $_dir; throw } } -ArgumentList $_.FullName }; $jobs | Wait-Job; $jobs | ForEach-Object { Receive-Job -Job $_ -Keep } | ForEach-Object { Write-Host $_ }; $failed = $jobs | Where-Object { $_.State -ne 'Completed' -or $_.ChildJobs[0].JobStateInfo.State -ne 'Completed' }; if ($failed) { Exit 1 }\"", - "build:extensions:linux": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", - "build:extensions:darwin": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", + "build:extensions:windows": "rimraf ./pre-install/*.tgz && powershell -command \"$jobs = Get-ChildItem -Path './extensions' -Directory | ForEach-Object { Start-Job -Name ($_.Name) -ScriptBlock { param($_dir); try { Set-Location $_dir; npm install; npm run build:publish; Write-Output 'Build successful in ' + $_dir } catch { Write-Error 'Error in ' + $_dir; throw } } -ArgumentList $_.FullName }; $jobs | Wait-Job; $jobs | ForEach-Object { Receive-Job -Job $_ -Keep } | ForEach-Object { Write-Host $_ }; $failed = $jobs | Where-Object { $_.State -ne 'Completed' -or $_.ChildJobs[0].JobStateInfo.State -ne 'Completed' }; if ($failed) { Exit 1 }\"", + "build:extensions:linux": "rimraf ./pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", + "build:extensions:darwin": "rimraf ./pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", + "build:extensions:server": "yarn workspace build:extensions ", "build:extensions": "run-script-os", "build:test": "yarn copy:assets && yarn build:web && yarn workspace jan build:test", "build": "yarn build:web && yarn build:electron", diff --git a/pre-install/.gitkeep b/pre-install/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/helpers/setup.ts b/server/helpers/setup.ts new file mode 100644 index 000000000..51d8eebe5 --- /dev/null +++ b/server/helpers/setup.ts @@ -0,0 +1,47 @@ +import { join, extname } from "path"; +import { existsSync, readdirSync, writeFileSync, mkdirSync } from "fs"; +import { init, installExtensions } from "@janhq/core/node"; + +export async function setup() { + /** + * Setup Jan Data Directory + */ + const appDir = process.env.JAN_DATA_DIRECTORY ?? join(__dirname, "..", "jan"); + + console.debug(`Create app data directory at ${appDir}...`); + if (!existsSync(appDir)) mkdirSync(appDir); + //@ts-ignore + global.core = { + // Define appPath function for app to retrieve app path globaly + appPath: () => appDir, + }; + init({ + extensionsPath: join(appDir, "extensions"), + }); + + /** + * Write app configurations. See #1619 + */ + console.debug("Writing config file..."); + writeFileSync( + join(appDir, "settings.json"), + JSON.stringify({ + data_folder: appDir, + }), + "utf-8" + ); + + /** + * Install extensions + */ + + console.debug("Installing extensions..."); + + const baseExtensionPath = join(__dirname, "../../..", "pre-install"); + const extensions = readdirSync(baseExtensionPath) + .filter((file) => extname(file) === ".tgz") + .map((file) => join(baseExtensionPath, file)); + + await installExtensions(extensions); + console.debug("Extensions installed"); +} diff --git a/server/index.ts b/server/index.ts index 05bfdca96..91349a81f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -38,6 +38,7 @@ export interface ServerConfig { isVerboseEnabled?: boolean; schemaPath?: string; baseDir?: string; + storageAdataper?: any; } /** @@ -103,9 +104,12 @@ export const startServer = async (configs?: ServerConfig) => { { prefix: "extensions" } ); + // Register proxy middleware + if (configs?.storageAdataper) + server.addHook("preHandler", configs.storageAdataper); + // Register API routes await server.register(v1Router, { prefix: "/v1" }); - // Start listening for requests await server .listen({ diff --git a/server/main.ts b/server/main.ts index c3eb69135..3be397e6f 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,3 +1,7 @@ -import { startServer } from "./index"; - -startServer(); +import { s3 } from "./middleware/s3"; +import { setup } from "./helpers/setup"; +import { startServer as start } from "./index"; +/** + * Setup extensions and start the server + */ +setup().then(() => start({ storageAdataper: s3 })); diff --git a/server/middleware/s3.ts b/server/middleware/s3.ts new file mode 100644 index 000000000..624865222 --- /dev/null +++ b/server/middleware/s3.ts @@ -0,0 +1,70 @@ +import { join } from "path"; + +// Middleware to intercept requests and proxy if certain conditions are met +const config = { + endpoint: process.env.AWS_ENDPOINT, + region: process.env.AWS_REGION, + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }, +}; + +const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME; + +const fs = require("@cyclic.sh/s3fs")(S3_BUCKET_NAME, config); +const PROXY_PREFIX = "/v1/fs"; +const PROXY_ROUTES = ["/threads", "/messages"]; + +export const s3 = (req: any, reply: any, done: any) => { + // Proxy FS requests to S3 using S3FS + if (req.url.startsWith(PROXY_PREFIX)) { + const route = req.url.split("/").pop(); + const args = parseRequestArgs(req); + + // Proxy matched requests to the s3fs module + if (args.length && PROXY_ROUTES.some((route) => args[0].includes(route))) { + try { + // Handle customized route + // S3FS does not handle appendFileSync + if (route === "appendFileSync") { + let result = handAppendFileSync(args); + + reply.status(200).send(result); + return; + } + // Reroute the other requests to the s3fs module + const result = fs[route](...args); + reply.status(200).send(result); + return; + } catch (ex) { + console.log(ex); + } + } + } + // Let other requests go through + done(); +}; + +const parseRequestArgs = (req: Request) => { + const { + getJanDataFolderPath, + normalizeFilePath, + } = require("@janhq/core/node"); + + return JSON.parse(req.body as any).map((arg: any) => + typeof arg === "string" && + (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`)) + ? join(getJanDataFolderPath(), normalizeFilePath(arg)) + : arg + ); +}; + +const handAppendFileSync = (args: any[]) => { + if (fs.existsSync(args[0])) { + const data = fs.readFileSync(args[0], "utf-8"); + return fs.writeFileSync(args[0], data + args[1]); + } else { + return fs.writeFileSync(args[0], args[1]); + } +}; diff --git a/server/nodemon.json b/server/nodemon.json deleted file mode 100644 index 0ea41ca96..000000000 --- a/server/nodemon.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "watch": ["main.ts", "v1"], - "ext": "ts, json", - "exec": "tsc && node ./build/main.js" -} \ No newline at end of file diff --git a/server/package.json b/server/package.json index f61730da4..c1a104506 100644 --- a/server/package.json +++ b/server/package.json @@ -13,16 +13,18 @@ "scripts": { "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", "test:e2e": "playwright test --workers=1", - "dev": "tsc --watch & node --watch build/main.js", - "build": "tsc" + "build:core": "cd node_modules/@janhq/core && yarn install && yarn build", + "dev": "yarn build:core && tsc --watch & node --watch build/main.js", + "build": "yarn build:core && tsc" }, "dependencies": { "@alumna/reflect": "^1.1.3", + "@cyclic.sh/s3fs": "^1.2.9", "@fastify/cors": "^8.4.2", "@fastify/static": "^6.12.0", "@fastify/swagger": "^8.13.0", "@fastify/swagger-ui": "2.0.1", - "@janhq/core": "link:./core", + "@janhq/core": "file:../core", "dotenv": "^16.3.1", "fastify": "^4.24.3", "request": "^2.88.2", @@ -39,5 +41,8 @@ "run-script-os": "^1.1.6", "@types/tcp-port-used": "^1.0.4", "typescript": "^5.2.2" - } + }, + "bundleDependencies": [ + "@janhq/core" + ] } diff --git a/server/tsconfig.json b/server/tsconfig.json index 2c4fc4a64..dd27b8932 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -20,5 +20,5 @@ // "sourceMap": true, "include": ["./**/*.ts"], - "exclude": ["core", "build", "dist", "tests", "node_modules"] + "exclude": ["core", "build", "dist", "tests", "node_modules", "extensions"] } diff --git a/web/containers/Layout/BottomBar/DownloadingState/index.tsx b/web/containers/Layout/BottomBar/DownloadingState/index.tsx index 7aef36caf..c7191d0b9 100644 --- a/web/containers/Layout/BottomBar/DownloadingState/index.tsx +++ b/web/containers/Layout/BottomBar/DownloadingState/index.tsx @@ -13,22 +13,22 @@ import { import { useAtomValue } from 'jotai' import useDownloadModel from '@/hooks/useDownloadModel' -import { useDownloadState } from '@/hooks/useDownloadState' +import { modelDownloadStateAtom } from '@/hooks/useDownloadState' import { formatDownloadPercentage } from '@/utils/converter' -import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' +import { getDownloadingModelAtom } from '@/helpers/atoms/Model.atom' export default function DownloadingState() { - const { downloadStates } = useDownloadState() - const downloadingModels = useAtomValue(downloadingModelsAtom) + const downloadStates = useAtomValue(modelDownloadStateAtom) + const downloadingModels = useAtomValue(getDownloadingModelAtom) const { abortModelDownload } = useDownloadModel() - const totalCurrentProgress = downloadStates + const totalCurrentProgress = Object.values(downloadStates) .map((a) => a.size.transferred + a.size.transferred) .reduce((partialSum, a) => partialSum + a, 0) - const totalSize = downloadStates + const totalSize = Object.values(downloadStates) .map((a) => a.size.total + a.size.total) .reduce((partialSum, a) => partialSum + a, 0) @@ -36,12 +36,14 @@ export default function DownloadingState() { return ( - {downloadStates?.length > 0 && ( + {Object.values(downloadStates)?.length > 0 && (
    Downloading model - {downloadStates.map((item, i) => { - return ( -
    - -
    -
    -

    {item?.modelId}

    - {formatDownloadPercentage(item?.percent)} -
    - + {Object.values(downloadStates).map((item, i) => ( +
    + +
    +
    +

    {item?.modelId}

    + {formatDownloadPercentage(item?.percent)}
    +
    - ) - })} +
    + ))} )} diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 32dc70c70..5021b821c 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -25,8 +25,7 @@ import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' -import { useDownloadState } from '@/hooks/useDownloadState' - +import { modelDownloadStateAtom } from '@/hooks/useDownloadState' import useGetSystemResources from '@/hooks/useGetSystemResources' import { useMainViewState } from '@/hooks/useMainViewState' @@ -53,7 +52,7 @@ const BottomBar = () => { const downloadedModels = useAtomValue(downloadedModelsAtom) const { setMainViewState } = useMainViewState() - const { downloadStates } = useDownloadState() + const downloadStates = useAtomValue(modelDownloadStateAtom) const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom) const [serverEnabled] = useAtom(serverEnabledAtom) @@ -109,7 +108,7 @@ const BottomBar = () => { )} {downloadedModels.length === 0 && !stateModel.loading && - downloadStates.length === 0 && ( + Object.values(downloadStates).length === 0 && ( - ) - - if (isDownloaded) { - downloadButton = ( - - ) - } - - if (downloadState != null && downloadStates.length > 0) { - downloadButton = - } - - return ( -
    -
    - - {model.name} - -
    -
    -
    - {downloadButton} -
    -
    - ) -} - -export default ModelVersionItem diff --git a/web/screens/ExploreModels/ModelVersionList/index.tsx b/web/screens/ExploreModels/ModelVersionList/index.tsx deleted file mode 100644 index 7992b7a51..000000000 --- a/web/screens/ExploreModels/ModelVersionList/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Model } from '@janhq/core' - -import ModelVersionItem from '../ModelVersionItem' - -type Props = { - models: Model[] - recommendedVersion: string -} - -export default function ModelVersionList({ - models, - recommendedVersion, -}: Props) { - return ( -
    - {models.map((model) => ( - - ))} -
    - ) -} diff --git a/web/services/restService.ts b/web/services/restService.ts index 25488ae15..6b749fd71 100644 --- a/web/services/restService.ts +++ b/web/services/restService.ts @@ -3,6 +3,7 @@ import { AppRoute, DownloadRoute, ExtensionRoute, + FileManagerRoute, FileSystemRoute, } from '@janhq/core' @@ -22,6 +23,7 @@ export const APIRoutes = [ route: r, })), ...Object.values(FileSystemRoute).map((r) => ({ path: `fs`, route: r })), + ...Object.values(FileManagerRoute).map((r) => ({ path: `fs`, route: r })), ] // Define the restAPI object with methods for each API route @@ -50,4 +52,6 @@ export const restAPI = { } }, {}), openExternalUrl, + // Jan Server URL + baseApiUrl: API_BASE_URL, } diff --git a/web/types/downloadState.d.ts b/web/types/downloadState.d.ts index cca526bf1..766a0bde5 100644 --- a/web/types/downloadState.d.ts +++ b/web/types/downloadState.d.ts @@ -1,12 +1,13 @@ type DownloadState = { modelId: string + filename: string time: DownloadTime speed: number percent: number size: DownloadSize - isFinished?: boolean children?: DownloadState[] error?: string + downloadState: 'downloading' | 'error' | 'end' } type DownloadTime = { From 9a1b1adc72961b37fe6d23b6c26de60b91fb2924 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Wed, 7 Feb 2024 18:07:12 +0700 Subject: [PATCH 50/98] fix: update conditional check last status message (#1951) * fix: update conditional check last status message * fix: no new message on failed message --------- Co-authored-by: Louis --- web/containers/Providers/EventHandler.tsx | 6 +++--- web/helpers/atoms/ChatMessage.atom.ts | 14 ++++++++------ web/hooks/useCreateNewThread.ts | 7 +++++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index f22ed1bc7..cfd2c5629 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -128,10 +128,10 @@ export default function EventHandler({ children }: { children: ReactNode }) { const thread = threadsRef.current?.find((e) => e.id == message.thread_id) if (thread) { - const messageContent = message.content[0]?.text.value ?? '' + const messageContent = message.content[0]?.text?.value const metadata = { ...thread.metadata, - lastMessage: messageContent, + ...(messageContent && { lastMessage: messageContent }), } updateThread({ @@ -151,7 +151,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { ?.addNewMessage(message) } }, - [updateMessage, updateThreadWaiting, setIsGeneratingResponse] + [updateMessage, updateThreadWaiting, setIsGeneratingResponse, updateThread] ) useEffect(() => { diff --git a/web/helpers/atoms/ChatMessage.atom.ts b/web/helpers/atoms/ChatMessage.atom.ts index b11e8f3be..45cc773e6 100644 --- a/web/helpers/atoms/ChatMessage.atom.ts +++ b/web/helpers/atoms/ChatMessage.atom.ts @@ -70,11 +70,12 @@ export const addNewMessageAtom = atom( set(chatMessages, newData) // Update thread last message - set( - updateThreadStateLastMessageAtom, - newMessage.thread_id, - newMessage.content - ) + if (newMessage.content.length) + set( + updateThreadStateLastMessageAtom, + newMessage.thread_id, + newMessage.content + ) } ) @@ -131,7 +132,8 @@ export const updateMessageAtom = atom( newData[conversationId] = updatedMessages set(chatMessages, newData) // Update thread last message - set(updateThreadStateLastMessageAtom, conversationId, text) + if (text.length) + set(updateThreadStateLastMessageAtom, conversationId, text) } } ) diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts index 12a5e04ca..406bf8f74 100644 --- a/web/hooks/useCreateNewThread.ts +++ b/web/hooks/useCreateNewThread.ts @@ -6,6 +6,7 @@ import { ThreadAssistantInfo, ThreadState, Model, + MessageStatus, } from '@janhq/core' import { atom, useAtomValue, useSetAtom } from 'jotai' @@ -20,6 +21,7 @@ import useSetActiveThread from './useSetActiveThread' import { extensionManager } from '@/extension' +import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { threadsAtom, threadStatesAtom, @@ -51,6 +53,7 @@ export const useCreateNewThread = () => { const setFileUpload = useSetAtom(fileUploadAtom) const setSelectedModel = useSetAtom(selectedModelAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) + const messages = useAtomValue(getCurrentChatMessagesAtom) const { recommendedModel, downloadedModels } = useRecommendedModel() @@ -63,9 +66,9 @@ export const useCreateNewThread = () => { const defaultModel = model ?? recommendedModel ?? downloadedModels[0] // check last thread message, if there empty last message use can not create thread - const lastMessage = threads[0]?.metadata?.lastMessage + const lastMessage = threads[threads.length - 1]?.metadata?.lastMessage - if (!lastMessage && threads.length) { + if (!lastMessage && threads.length && !messages.length) { return null } From 44a7f0590b11ef00acd90c1d7db5bc987cc026d7 Mon Sep 17 00:00:00 2001 From: Hoang Ha <64120343+hahuyhoang411@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:12:32 +0700 Subject: [PATCH 51/98] fix: authors.yml --- docs/blog/authors.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/blog/authors.yml b/docs/blog/authors.yml index fe344cacb..482ef0788 100644 --- a/docs/blog/authors.yml +++ b/docs/blog/authors.yml @@ -35,8 +35,14 @@ Van-QA: hahuyhoang411: name: Rex Ha - title: LLM Researcher & Content writer + title: LLM Researcher & Content Writer url: https://github.com/hahuyhoang411 image_url: https://avatars.githubusercontent.com/u/64120343?v=4 email: rex@jan.ai - + +automaticcat: + name: Alan Dao + title: AI Engineer + url: https://github.com/tikikun + image_url: https://avatars.githubusercontent.com/u/22268502?v=4 + email: alan@jan.ai From fd36310bb3a17d27c857125479857b200d343b34 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:23:35 +0700 Subject: [PATCH 52/98] feat: integrate umami script locally --- web/public/umami_script.js | 148 +++++++++++++++++++++++++++++++++++++ web/utils/umami.tsx | 86 ++++++++++++++------- 2 files changed, 209 insertions(+), 25 deletions(-) create mode 100644 web/public/umami_script.js diff --git a/web/public/umami_script.js b/web/public/umami_script.js new file mode 100644 index 000000000..f9b5b7fcf --- /dev/null +++ b/web/public/umami_script.js @@ -0,0 +1,148 @@ +! function() { + "use strict"; + ! function(t) { + var e = t.screen, + n = e.width, + r = e.height, + a = t.navigator.language, + i = t.location, + o = t.localStorage, + u = t.document, + c = t.history, + f = "jan.ai", + s = "main page", + l = i.search, + d = u.currentScript; + if (d) { + var m = "data-", + h = d.getAttribute.bind(d), + v = h(m + "website-id"), + p = h(m + "host-url"), + g = "false" !== h(m + "auto-track"), + y = h(m + "do-not-track"), + b = h(m + "domains") || "", + S = b.split(",").map((function(t) { + return t.trim() + })), + k = (p ? p.replace(/\/$/, "") : d.src.split("/").slice(0, -1).join("/")) + "/api/send", + w = n + "x" + r, + N = /data-umami-event-([\w-_]+)/, + T = m + "umami-event", + j = 300, + A = function(t, e, n) { + var r = t[e]; + return function() { + for (var e = [], a = arguments.length; a--;) e[a] = arguments[a]; + return n.apply(null, e), r.apply(t, e) + } + }, + x = function() { + return { + website: v, + hostname: f, + screen: w, + language: a, + title: M, + url: I, + referrer: J + } + }, + E = function() { + return o && o.getItem("umami.disabled") || y && function() { + var e = t.doNotTrack, + n = t.navigator, + r = t.external, + a = "msTrackingProtectionEnabled", + i = e || n.doNotTrack || n.msDoNotTrack || r && a in r && r[a](); + return "1" == i || "yes" === i + }() || b && !S.includes(f) + }, + O = function(t, e, n) { + n && (J = I, (I = function(t) { + try { + return new URL(t).pathname + } catch (e) { + return t + } + }(n.toString())) !== J && setTimeout(D, j)) + }, + L = function(t, e) { + if (void 0 === e && (e = "event"), !E()) { + var n = { + "Content-Type": "application/json" + }; + return void 0 !== K && (n["x-umami-cache"] = K), fetch(k, { + method: "POST", + body: JSON.stringify({ + type: e, + payload: t + }), + headers: n + }).then((function(t) { + return t.text() + })).then((function(t) { + return K = t + })).catch((function() {})) + } + }, + D = function(t, e) { + return L("string" == typeof t ? Object.assign({}, x(), { + name: t, + data: "object" == typeof e ? e : void 0 + }) : "object" == typeof t ? t : "function" == typeof t ? t(x()) : x()) + }; + t.umami || (t.umami = { + track: D, + identify: function(t) { + return L(Object.assign({}, x(), { + data: t + }), "identify") + } + }); + var K, P, _, q, C, I = "" + s + l, + J = u.referrer, + M = u.title; + if (g && !E()) { + c.pushState = A(c, "pushState", O), c.replaceState = A(c, "replaceState", O), C = function(t) { + var e = t.getAttribute.bind(t), + n = e(T); + if (n) { + var r = {}; + return t.getAttributeNames().forEach((function(t) { + var n = t.match(N); + n && (r[n[1]] = e(t)) + })), D(n, r) + } + return Promise.resolve() + }, u.addEventListener("click", (function(t) { + var e = t.target, + n = "A" === e.tagName ? e : function(t, e) { + for (var n = t, r = 0; r < e; r++) { + if ("A" === n.tagName) return n; + if (!(n = n.parentElement)) return null + } + return null + }(e, 10); + if (n) { + var r = n.href, + a = "_blank" === n.target || t.ctrlKey || t.shiftKey || t.metaKey || t.button && 1 === t.button; + if (n.getAttribute(T) && r) return a || t.preventDefault(), C(n).then((function() { + a || (i.href = r) + })) + } else C(e) + }), !0), _ = new MutationObserver((function(t) { + var e = t[0]; + M = e && e.target ? e.target.text : void 0 + })), (q = u.querySelector("head > title")) && _.observe(q, { + subtree: !0, + characterData: !0, + childList: !0 + }); + var R = function() { + "complete" !== u.readyState || P || (D(), P = !0) + }; + u.addEventListener("readystatechange", R, !0), R() + } + } + }(window) +}(); \ No newline at end of file diff --git a/web/utils/umami.tsx b/web/utils/umami.tsx index 277ae1223..9e678d77f 100644 --- a/web/utils/umami.tsx +++ b/web/utils/umami.tsx @@ -1,31 +1,67 @@ import { useEffect } from 'react' -const Umami = () => { - useEffect(() => { - if (!VERSION || !ANALYTICS_HOST || !ANALYTICS_ID) return - fetch(ANALYTICS_HOST, { - method: 'POST', - // eslint-disable-next-line @typescript-eslint/naming-convention - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - payload: { - website: ANALYTICS_ID, - hostname: 'jan.ai', - screen: `${screen.width}x${screen.height}`, - language: navigator.language, - referrer: 'index.html', - data: { version: VERSION }, - type: 'event', - title: document.title, - url: 'index.html', - name: VERSION, - }, - type: 'event', - }), - }) - }, []) +import Script from 'next/script' - return <> +// Define the type for the umami data object +interface UmamiData { + version: string +} + +declare global { + interface Window { + umami: + | { + track: (event: string, data?: UmamiData) => void + } + | undefined + } +} + +const Umami = () => { + const appVersion = VERSION + const analyticsScriptPath = './umami_script.js' + const analyticsId = ANALYTICS_ID + + useEffect(() => { + if (!appVersion || !analyticsScriptPath || !analyticsId) return + + const ping = () => { + // Check if umami is defined before ping + if (window.umami !== null && typeof window.umami !== 'undefined') { + window.umami.track(appVersion, { + version: appVersion, + }) + } + } + + // Wait for umami to be defined before ping + if (window.umami !== null && typeof window.umami !== 'undefined') { + ping() + } else { + // Listen for umami script load event + document.addEventListener('umami:loaded', ping) + } + + // Cleanup function to remove event listener if the component unmounts + return () => { + document.removeEventListener('umami:loaded', ping) + } + }, [appVersion, analyticsScriptPath, analyticsId]) + + return ( + <> + {appVersion && analyticsScriptPath && analyticsId && ( +
    { From d5830b3fbffab8f6b017f12ea2b4c2442d216659 Mon Sep 17 00:00:00 2001 From: Van Pham <64197333+Van-QA@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:08:14 +0700 Subject: [PATCH 34/98] adding new feature for v0.4.6 (#1927) Move Jan Data App: https://github.com/janhq/jan/issues/1010 Factory Settings: https://github.com/janhq/jan/issues/1620 --- docs/docs/template/QA_script.md | 59 +++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/docs/docs/template/QA_script.md b/docs/docs/template/QA_script.md index 05dbed2b4..bba667bcd 100644 --- a/docs/docs/template/QA_script.md +++ b/docs/docs/template/QA_script.md @@ -1,6 +1,6 @@ # [Release Version] QA Script -**Release Version:** +**Release Version:** v0.4.6 **Operating System:** @@ -25,10 +25,10 @@ ### 3. Users uninstall app -- [ ] :key: Check that the uninstallation process removes all components of the app from the system. +- [ ] :key::warning: Check that the uninstallation process removes the app successfully from the system. - [ ] Clean the Jan root directory and open the app to check if it creates all the necessary folders, especially models and extensions. - [ ] When updating the app, check if the `/models` directory has any JSON files that change according to the update. -- [ ] Verify if updating the app also updates extensions correctly (test functionality changes; support notifications for necessary tests with each version related to extensions update). +- [ ] Verify if updating the app also updates extensions correctly (test functionality changes, support notifications for necessary tests with each version related to extensions update). ### 4. Users close app @@ -60,49 +60,45 @@ - [ ] :key: Ensure that the conversation thread is maintained without any loss of data upon sending multiple messages. - [ ] Test for the ability to send different types of messages (e.g., text, emojis, code blocks). - [ ] :key: Validate the scroll functionality in the chat window for lengthy conversations. -- [ ] Check if the user can renew responses multiple times. - [ ] Check if the user can copy the response. - [ ] Check if the user can delete responses. -- [ ] :warning: Test if the user deletes the message midway, then the assistant stops that response. - [ ] :key: Check the `clear message` button works. - [ ] :key: Check the `delete entire chat` works. -- [ ] :warning: Check if deleting all the chat retains the system prompt. +- [ ] Check if deleting all the chat retains the system prompt. - [ ] Check the output format of the AI (code blocks, JSON, markdown, ...). - [ ] :key: Validate that there is appropriate error handling and messaging if the assistant fails to respond. - [ ] Test assistant's ability to maintain context over multiple exchanges. - [ ] :key: Check the `create new chat` button works correctly - [ ] Confirm that by changing `models` mid-thread the app can still handle it. -- [ ] Check that by changing `instructions` mid-thread the app can still handle it. -- [ ] Check the `regenerate` button renews the response. -- [ ] Check the `Instructions` update correctly after the user updates it midway. +- [ ] Check the `regenerate` button renews the response (single / multiple times). +- [ ] Check the `Instructions` update correctly after the user updates it midway (mid-thread). ### 2. Users can customize chat settings like model parameters via both the GUI & thread.json -- [ ] :key: Confirm that the chat settings options are accessible via the GUI. +- [ ] :key: Confirm that the Threads settings options are accessible. - [ ] Test the functionality to adjust model parameters (e.g., Temperature, Top K, Top P) from the GUI and verify they are reflected in the chat behavior. - [ ] :key: Ensure that changes can be saved and persisted between sessions. - [ ] Validate that users can access and modify the thread.json file. - [ ] :key: Check that changes made in thread.json are correctly applied to the chat session upon reload or restart. -- [ ] Verify if there is a revert option to go back to previous settings after changes are made. -- [ ] Test for user feedback or confirmation after saving changes to settings. - [ ] Check the maximum and minimum limits of the adjustable parameters and how they affect the assistant's responses. - [ ] :key: Validate user permissions for those who can change settings and persist them. - [ ] :key: Ensure that users switch between threads with different models, the app can handle it. -### 3. Users can click on a history thread +### 3. Model dropdown +- [ ] :key: Model list should highlight recommended based on user RAM +- [ ] Model size should display (for both installed and imported models) +### 4. Users can click on a history thread - [ ] Test the ability to click on any thread in the history panel. - [ ] :key: Verify that clicking a thread brings up the past conversation in the main chat window. - [ ] :key: Ensure that the selected thread is highlighted or otherwise indicated in the history panel. - [ ] Confirm that the chat window displays the entire conversation from the selected history thread without any missing messages. - [ ] :key: Check the performance and accuracy of the history feature when dealing with a large number of threads. - [ ] Validate that historical threads reflect the exact state of the chat at that time, including settings. -- [ ] :key: :warning: Test the search functionality within the history panel for quick navigation. - [ ] :key: Verify the ability to delete or clean old threads. - [ ] :key: Confirm that changing the title of the thread updates correctly. -### 4. Users can config instructions for the assistant. - +### 5. Users can config instructions for the assistant. - [ ] Ensure there is a clear interface to input or change instructions for the assistant. - [ ] Test if the instructions set by the user are being followed by the assistant in subsequent conversations. - [ ] :key: Validate that changes to instructions are updated in real time and do not require a restart of the application or session. @@ -112,6 +108,8 @@ - [ ] Validate that instructions can be saved with descriptive names for easy retrieval. - [ ] :key: Check if the assistant can handle conflicting instructions and how it resolves them. - [ ] Ensure that instruction configurations are documented for user reference. +- [ ] :key: RAG - Users can import documents and the system should process queries about the uploaded file, providing accurate and appropriate responses in the conversation thread. + ## D. Hub @@ -125,8 +123,7 @@ - [ ] Display the best model for their RAM at the top. - [ ] :key: Ensure that models are labeled with RAM requirements and compatibility. -- [ ] :key: Validate that the download function is disabled for models that exceed the user's system capabilities. -- [ ] Test that the platform provides alternative recommendations for models not suitable due to RAM limitations. +- [ ] :warning: Test that the platform provides alternative recommendations for models not suitable due to RAM limitations. - [ ] :key: Check the download model functionality and validate if the cancel download feature works correctly. ### 3. Users can download models via a HuggingFace URL (coming soon) @@ -139,7 +136,7 @@ - [ ] :key: Have clear instructions so users can do their own. - [ ] :key: Ensure the new model updates after restarting the app. -- [ ] Ensure it raises clear errors for users to fix the problem while adding a new model. +- [ ] :warning:Ensure it raises clear errors for users to fix the problem while adding a new model. ### 5. Users can use the model as they want @@ -149,9 +146,13 @@ - [ ] Check if starting another model stops the other model entirely. - [ ] Check the `Explore models` navigate correctly to the model panel. - [ ] :key: Check when deleting a model it will delete all the files on the user's computer. -- [ ] The recommended tags should present right for the user's hardware. +- [ ] :warning:The recommended tags should present right for the user's hardware. - [ ] Assess that the descriptions of models are accurate and informative. +### 6. Users can Integrate With a Remote Server +- [ ] :key: Import openAI GPT model https://jan.ai/guides/using-models/integrate-with-remote-server/ and the model displayed in Hub / Thread dropdown +- [ ] Users can use the remote model properly + ## E. System Monitor ### 1. Users can see disk and RAM utilization @@ -181,7 +182,7 @@ - [ ] Confirm that the application saves the theme preference and persists it across sessions. - [ ] Validate that all elements of the UI are compatible with the theme changes and maintain legibility and contrast. -### 2. Users change the extensions +### 2. Users change the extensions [TBU] - [ ] Confirm that the `Extensions` tab lists all available plugins. - [ ] :key: Test the toggle switch for each plugin to ensure it enables or disables the plugin correctly. @@ -208,3 +209,19 @@ - [ ] :key: Test that the application prevents the installation of incompatible or corrupt plugin files. - [ ] :key: Check that the user can uninstall or disable custom plugins as easily as pre-installed ones. - [ ] Verify that the application's performance remains stable after the installation of custom plugins. + +### 5. Advanced Settings +- [ ] Attemp to test downloading model from hub using **HTTP Proxy** [guideline](https://github.com/janhq/jan/pull/1562) +- [ ] Users can move **Jan data folder** +- [ ] Users can click on Reset button to **factory reset** app settings to its original state & delete all usage data. + +## G. Local API server + +### 1. Local Server Usage with Server Options +- [ ] :key: Explore API Reference: Swagger API for sending/receiving requests + - [ ] Use default server option + - [ ] Configure and use custom server options +- [ ] Test starting/stopping the local API server with different Model/Model settings +- [ ] Server logs captured with correct Server Options provided +- [ ] Verify functionality of Open logs/Clear feature +- [ ] Ensure that threads and other functions impacting the model are disabled while the local server is running From 0d152a25f08d8defb1d2d8b2058330ccfbeacfe9 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 5 Feb 2024 09:16:42 +0000 Subject: [PATCH 35/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e1a06820e..007d81cc5 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 61419e5c0205618b5b4774aa3ba03b9802b8461e Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 5 Feb 2024 20:20:35 +0000 Subject: [PATCH 36/98] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 007d81cc5..b84e03e7f 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 29a7fb8c93cffb32c2f89b0775f408ec3fe64789 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 6 Feb 2024 16:00:29 +0700 Subject: [PATCH 37/98] fix: avoid users to create so many threads at the same time (#1930) * fix: avoid allow users to create so many threads at the same time * fix missing last message * remove console * update last message metadata thread * update conditional statement --- web/containers/Providers/EventHandler.tsx | 8 ++++++++ web/hooks/useCreateNewThread.ts | 12 +++++++++++- web/screens/Chat/ThreadList/index.tsx | 4 +++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index e9d70d5d2..f22ed1bc7 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -33,6 +33,7 @@ import { updateThreadWaitingForResponseAtom, threadsAtom, isGeneratingResponseAtom, + updateThreadAtom, } from '@/helpers/atoms/Thread.atom' export default function EventHandler({ children }: { children: ReactNode }) { @@ -49,6 +50,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { const modelsRef = useRef(downloadedModels) const threadsRef = useRef(threads) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) + const updateThread = useSetAtom(updateThreadAtom) useEffect(() => { threadsRef.current = threads @@ -131,6 +133,12 @@ export default function EventHandler({ children }: { children: ReactNode }) { ...thread.metadata, lastMessage: messageContent, } + + updateThread({ + ...thread, + metadata, + }) + extensionManager .get(ExtensionTypeEnum.Conversational) ?.saveThread({ diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts index ee8df22df..12a5e04ca 100644 --- a/web/hooks/useCreateNewThread.ts +++ b/web/hooks/useCreateNewThread.ts @@ -7,7 +7,7 @@ import { ThreadState, Model, } from '@janhq/core' -import { atom, useSetAtom } from 'jotai' +import { atom, useAtomValue, useSetAtom } from 'jotai' import { selectedModelAtom } from '@/containers/DropdownListSidebar' import { fileUploadAtom } from '@/containers/Providers/Jotai' @@ -19,6 +19,7 @@ import useRecommendedModel from './useRecommendedModel' import useSetActiveThread from './useSetActiveThread' import { extensionManager } from '@/extension' + import { threadsAtom, threadStatesAtom, @@ -53,12 +54,21 @@ export const useCreateNewThread = () => { const { recommendedModel, downloadedModels } = useRecommendedModel() + const threads = useAtomValue(threadsAtom) + const requestCreateNewThread = async ( assistant: Assistant, model?: Model | undefined ) => { const defaultModel = model ?? recommendedModel ?? downloadedModels[0] + // check last thread message, if there empty last message use can not create thread + const lastMessage = threads[0]?.metadata?.lastMessage + + if (!lastMessage && threads.length) { + return null + } + const createdAt = Date.now() const assistantInfo: ThreadAssistantInfo = { assistant_id: assistant.id, diff --git a/web/screens/Chat/ThreadList/index.tsx b/web/screens/Chat/ThreadList/index.tsx index 8f5bfb8f2..2ad9a28c4 100644 --- a/web/screens/Chat/ThreadList/index.tsx +++ b/web/screens/Chat/ThreadList/index.tsx @@ -62,7 +62,9 @@ export default function ThreadList() {

    {thread.title}

    - {threadStates[thread.id]?.lastMessage ?? 'No new message'} + {threadStates[thread.id]?.lastMessage + ? threadStates[thread.id]?.lastMessage + : 'No new message'}