diff --git a/core/src/browser/extensions/model.ts b/core/src/browser/extensions/model.ts index 9a34289889..da5ecd62e4 100644 --- a/core/src/browser/extensions/model.ts +++ b/core/src/browser/extensions/model.ts @@ -1,10 +1,13 @@ import { BaseExtension, ExtensionTypeEnum } from '../extension' -import { Model, ModelInterface, OptionType } from '../../types' +import { Model, ModelInterface, ModelSource, OptionType } from '../../types' /** * Model extension for managing models. */ -export abstract class ModelExtension extends BaseExtension implements ModelInterface { +export abstract class ModelExtension + extends BaseExtension + implements ModelInterface +{ /** * Model extension type. */ @@ -25,4 +28,16 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter abstract updateModel(modelInfo: Partial): Promise abstract deleteModel(model: string): Promise abstract isModelLoaded(model: string): Promise + /** + * Get model sources + */ + abstract getSources(): Promise + /** + * Add a model source + */ + abstract addSource(source: string): Promise + /** + * Delete a model source + */ + abstract deleteSource(source: string): Promise } diff --git a/core/src/types/model/index.ts b/core/src/types/model/index.ts index fdbf018636..16140a1c64 100644 --- a/core/src/types/model/index.ts +++ b/core/src/types/model/index.ts @@ -2,3 +2,4 @@ export * from './modelEntity' export * from './modelInterface' export * from './modelEvent' export * from './modelImport' +export * from './modelSource' diff --git a/core/src/types/model/modelInterface.ts b/core/src/types/model/modelInterface.ts index 7ad1b136c8..a03de97a26 100644 --- a/core/src/types/model/modelInterface.ts +++ b/core/src/types/model/modelInterface.ts @@ -1,5 +1,6 @@ import { Model } from './modelEntity' import { OptionType } from './modelImport' +import { ModelSource } from './modelSource' /** * Model extension for managing models. @@ -50,4 +51,17 @@ export interface ModelInterface { name?: string, optionType?: OptionType ): Promise + + /** + * Get model sources + */ + getSources(): Promise + /** + * Add a model source + */ + addSource(source: string): Promise + /** + * Delete a model source + */ + deleteSource(source: string): Promise } diff --git a/core/src/types/model/modelSource.ts b/core/src/types/model/modelSource.ts new file mode 100644 index 0000000000..af1e75d72d --- /dev/null +++ b/core/src/types/model/modelSource.ts @@ -0,0 +1,67 @@ +/** + * GGUF Metadata of the model source + */ +export interface GGUF { + architecture: string + bos_token: string + chat_template: string + context_length: number + eos_token: string + total: number +} + +/** + * Card Metadata of the model source + */ +export interface CardData { + license: string + pipeline_tag: string +} + +/** + * Model Metadata of the model source + */ +export interface Metadata { + author: string + cardData: CardData + createdAt: string + description: string + disabled: boolean + downloads: number + gated: boolean + gguf: GGUF + id: string + inference: string + lastModified: string + likes: number + modelId: string + pipeline_tag: string + private: boolean + sha: string + siblings: Array<{ + rfilename: string + size: number + }> + spaces: string[] + tags: string[] + usedStorage: number + apiKey?: string +} + +/** + * Model source sibling information + */ +export interface ModelSibling { + id: string + size: number +} + +/** + * Model source object + */ +export interface ModelSource { + id: string + metadata: Metadata + models: ModelSibling[] + type?: string +} diff --git a/electron/tests/e2e/hub.e2e.spec.ts b/electron/tests/e2e/hub.e2e.spec.ts index ef305e9c31..58d6a08544 100644 --- a/electron/tests/e2e/hub.e2e.spec.ts +++ b/electron/tests/e2e/hub.e2e.spec.ts @@ -17,7 +17,7 @@ test('explores hub', async ({ hubPage }) => { await hubPage.navigateByMenu() await hubPage.verifyContainerVisible() await hubPage.scrollToBottom() - const useModelBtn = page.getByTestId(/^use-model-btn-.*/).first() + const useModelBtn = page.getByTestId(/^setup-btn/).first() await expect(useModelBtn).toBeVisible({ timeout: TIMEOUT, diff --git a/electron/tests/e2e/thread.e2e.spec.ts b/electron/tests/e2e/thread.e2e.spec.ts index 312cb1f461..41efc84373 100644 --- a/electron/tests/e2e/thread.e2e.spec.ts +++ b/electron/tests/e2e/thread.e2e.spec.ts @@ -1,33 +1,18 @@ import { expect } from '@playwright/test' import { page, test, TIMEOUT } from '../config/fixtures' -test('Select GPT model from Hub and Chat with Invalid API Key', async ({ - hubPage, -}) => { - await hubPage.navigateByMenu() - await hubPage.verifyContainerVisible() - - // Select the first GPT model - await page - .locator('[data-testid^="use-model-btn"][data-testid*="gpt"]') - .first() - .click() - - await page.getByTestId('txt-input-chat').fill('dummy value') - +test('show onboarding screen without any threads created or models downloaded', async () => { + await page.getByTestId('Thread').first().click({ + timeout: TIMEOUT, + }) const denyButton = page.locator('[data-testid="btn-deny-product-analytics"]') if ((await denyButton.count()) > 0) { await denyButton.click({ force: true }) - } else { - await page.getByTestId('btn-send-chat').click({ force: true }) } - await page.waitForFunction( - () => { - const loaders = document.querySelectorAll('[data-testid$="loader"]') - return !loaders.length - }, - { timeout: TIMEOUT } - ) + const onboardScreen = page.getByTestId('onboard-screen') + await expect(onboardScreen).toBeVisible({ + timeout: TIMEOUT, + }) }) diff --git a/extensions/inference-cortex-extension/bin/version.txt b/extensions/inference-cortex-extension/bin/version.txt index d3b342f1a1..4a45ea2b2a 100644 --- a/extensions/inference-cortex-extension/bin/version.txt +++ b/extensions/inference-cortex-extension/bin/version.txt @@ -1 +1 @@ -1.0.9-rc4 +1.0.9-rc5 diff --git a/extensions/model-extension/resources/default.json b/extensions/model-extension/resources/default.json new file mode 100644 index 0000000000..7d58c55981 --- /dev/null +++ b/extensions/model-extension/resources/default.json @@ -0,0 +1,1434 @@ +[ + { + "id": "cortexso/deepseek-r1-distill-llama-70b", + "metadata": { + "_id": "678fe1673b0a6384a4e1f887", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-21T18:03:19.000Z", + "description": "---\nlicense: mit\n---\n\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Llama 70B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B) model, a distilled version of the Llama 70B language model. This model represents the pinnacle of the DeepSeek R1 Distill series, designed for exceptional performance in text generation, dialogue tasks, and advanced reasoning, offering unparalleled capabilities for large-scale AI applications.\n\nThe model is ideal for enterprise-grade applications, research, conversational AI, and large-scale knowledge systems, providing top-tier accuracy, safety, and efficiency.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-llama-70b/tree/main) | `cortex run deepseek-r1-distill-llama-70b` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```text\n cortexso/deepseek-r1-distill-llama-70b\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-llama-70b\n ```\n\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)\n", + "disabled": false, + "downloads": 6, + "gated": false, + "id": "cortexso/deepseek-r1-distill-llama-70b", + "inference": "library-not-detected", + "lastModified": "2025-01-23T08:58:56.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-llama-70b", + "private": false, + "sha": "59faddbe48125c56544917c3faff6c9f688167ee", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 310170138880 + }, + "models": [ + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q2-k", + "size": 26375110432 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q3-ks", + "size": 30912053024 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q4-km", + "size": 42520395552 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q5-ks", + "size": 48657448736 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q3-km", + "size": 34267496224 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q5-km", + "size": 49949818656 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q3-kl", + "size": 37140594464 + }, + { + "id": "deepseek-r1-distill-llama-70b:70b-gguf-q4-ks", + "size": 40347221792 + } + ] + }, + { + "id": "cortexso/command-r", + "metadata": { + "_id": "66751b98585f2bf57092b2ae", + "author": "cortexso", + "cardData": { + "license": "cc-by-nc-4.0" + }, + "createdAt": "2024-06-21T06:20:08.000Z", + "description": "---\nlicense: cc-by-nc-4.0\n---\n\n## Overview\n\nC4AI Command-R is a research release of a 35 billion parameter highly performant generative model. Command-R is a large language model with open weights optimized for a variety of use cases including reasoning, summarization, and question answering. Command-R has the capability for multilingual generation evaluated in 10 languages and highly performant RAG capabilities.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [35b-gguf](https://huggingface.co/cortexhub/command-r/tree/35b-gguf) | `cortex run command-r:35b-gguf` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexhub/command-r\n ```\n \n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run command-r\n ```\n \n## Credits\n\n- **Author:** Cohere For AI: [cohere.for.ai](https://cohere.for.ai/)\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [Licence](https://cohere.com/c4ai-cc-by-nc-license)", + "disabled": false, + "downloads": 9, + "gated": false, + "id": "cortexso/command-r", + "inference": "library-not-detected", + "lastModified": "2024-11-12T20:13:19.000Z", + "likes": 1, + "model-index": null, + "modelId": "cortexso/command-r", + "private": false, + "sha": "ca1564f6a6d4d03181b01e87e6c3e3fc959c7103", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:cc-by-nc-4.0", "region:us"], + "usedStorage": 227869888992 + }, + "models": [ + { + "id": "command-r:gguf", + "size": 21527041888 + }, + { + "id": "command-r:32b-gguf-q2-k", + "size": 12810767424 + }, + { + "id": "command-r:32b-gguf-q3-ks", + "size": 14708689984 + }, + { + "id": "command-r:32b-gguf-q3-kl", + "size": 17563438144 + }, + { + "id": "command-r:32b-gguf-q6-k", + "size": 26505169984 + }, + { + "id": "command-r:32b-gguf-q4-ks", + "size": 18849516608 + }, + { + "id": "command-r:35b-gguf", + "size": 21527041888 + }, + { + "id": "command-r:32b-gguf-q4-km", + "size": 19800837184 + }, + { + "id": "command-r:32b-gguf-q5-km", + "size": 23051422784 + }, + { + "id": "command-r:32b-gguf-q3-km", + "size": 16231746624 + }, + { + "id": "command-r:32b-gguf-q8-0", + "size": 34326891584 + }, + { + "id": "command-r:32b-gguf-q5-ks", + "size": 22494366784 + } + ] + }, + { + "id": "cortexso/deepseek-r1-distill-qwen-7b", + "metadata": { + "_id": "6790a5b2044aeb2bd5922877", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-22T08:00:50.000Z", + "description": "---\nlicense: mit\n---\n\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Qwen 7B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B) model, a distilled version of the Qwen 7B language model. This version is fine-tuned for high-performance text generation and optimized for dialogue and information-seeking tasks, providing even greater capabilities with its larger size compared to the 7B variant.\n\nThe model is designed for applications in customer support, conversational AI, and research, focusing on delivering accurate, helpful, and safe outputs while maintaining efficiency.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-qwen-7b/tree/main) | `cortex run deepseek-r1-distill-qwen-7b` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```text\n cortexso/deepseek-r1-distill-qwen-7b\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-qwen-7b\n ```\n\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)\n", + "disabled": false, + "downloads": 0, + "gated": false, + "id": "cortexso/deepseek-r1-distill-qwen-7b", + "inference": "library-not-detected", + "lastModified": "2025-01-23T08:43:37.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-qwen-7b", + "private": false, + "sha": "bbe804804125f9ace206eecd2e3040d8034189a6", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 48658728896 + }, + "models": [ + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q2-k", + "size": 3015939680 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q3-ks", + "size": 3492367968 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q4-ks", + "size": 4457768544 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q4-km", + "size": 4683073120 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q8-0", + "size": 8098524768 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q5-ks", + "size": 5315176032 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q3-kl", + "size": 4088458848 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q6-k", + "size": 6254198368 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q5-km", + "size": 5444830816 + }, + { + "id": "deepseek-r1-distill-qwen-7b:7b-gguf-q3-km", + "size": 3808390752 + } + ] + }, + { + "id": "cortexso/deepseek-r1-distill-qwen-14b", + "metadata": { + "_id": "678fdf2be186002cc0ba006e", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-21T17:53:47.000Z", + "description": "---\nlicense: mit\n---\n\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Qwen 14B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B) model, a distilled version of the Qwen 14B language model. This variant represents the largest and most powerful model in the DeepSeek R1 Distill series, fine-tuned for high-performance text generation, dialogue optimization, and advanced reasoning tasks. \n\nThe model is designed for applications that require extensive understanding, such as conversational AI, research, large-scale knowledge systems, and customer service, providing superior performance in accuracy, efficiency, and safety.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-qwen-14b/tree/main) | `cortex run deepseek-r1-distill-qwen-14b` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```text\n cortexso/deepseek-r1-distill-qwen-14b\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-qwen-14b\n ```\n\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)\n", + "disabled": false, + "downloads": 12, + "gated": false, + "id": "cortexso/deepseek-r1-distill-qwen-14b", + "inference": "library-not-detected", + "lastModified": "2025-01-23T08:48:43.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-qwen-14b", + "private": false, + "sha": "6ff0420f0bf32454e6b28180989d6b14687e19e6", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 93857311040 + }, + "models": [ + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q3-kl", + "size": 7924767776 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q2-k", + "size": 5770497056 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q4-ks", + "size": 8573430816 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q3-ks", + "size": 6659595296 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q4-km", + "size": 8988109856 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q6-k", + "size": 12124683296 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q5-ks", + "size": 10266553376 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q3-km", + "size": 7339203616 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q5-km", + "size": 10508872736 + }, + { + "id": "deepseek-r1-distill-qwen-14b:14b-gguf-q8-0", + "size": 15701597216 + } + ] + }, + { + "id": "cortexso/gemma2", + "metadata": { + "_id": "66b06c37491b555fefe0a0bf", + "author": "cortexso", + "cardData": { + "license": "gemma" + }, + "createdAt": "2024-08-05T06:07:51.000Z", + "description": "---\nlicense: gemma\n---\n\n## Overview\n\nThe [Gemma](https://huggingface.co/google/gemma-2-2b-it), state-of-the-art open model trained with the Gemma datasets that includes both synthetic data and the filtered publicly available websites data with a focus on high-quality and reasoning dense properties. The model belongs to the Gemma family with the 4B, 7B version in two variants 8K and 128K which is the context length (in tokens) that it can support.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [2b-gguf](https://huggingface.co/cortexso/gemma2/tree/2b-gguf) | `cortex run gemma:2b-gguf` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexso/gemma2\n ```\n \n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run gemma2\n ```\n \n## Credits\n\n- **Author:** Go\u200cogle\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://ai.google.dev/gemma/terms)\n- **Papers:** [Gemma Technical Report](https://arxiv.org/abs/2403.08295)", + "disabled": false, + "downloads": 284, + "gated": false, + "id": "cortexso/gemma2", + "inference": "library-not-detected", + "lastModified": "2024-11-12T20:13:02.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/gemma2", + "private": false, + "sha": "5fe1c79fabadcd2cb59cd05f76019d0a5fd71ce0", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["arxiv:2403.08295", "license:gemma", "region:us"], + "usedStorage": 265964141287 + }, + "models": [ + { + "id": "gemma2:2b-gguf-q3-km", + "size": 1461667584 + }, + { + "id": "gemma2:2b-gguf-q4-km", + "size": 1708582656 + }, + { + "id": "gemma2:2b-gguf-q6-k", + "size": 2151393024 + }, + { + "id": "gemma2:2b-gguf-q3-ks", + "size": 1360660224 + }, + { + "id": "gemma2:2b-gguf-q8-0", + "size": 2784495360 + }, + { + "id": "gemma2:2b-gguf-q4-ks", + "size": 1638651648 + }, + { + "id": "gemma2:9b-gguf-q3-ks", + "size": 4337665120 + }, + { + "id": "gemma2:gguf", + "size": 1708582496 + }, + { + "id": "gemma2:9b-gguf-q4-km", + "size": 5761057888 + }, + { + "id": "gemma2:9b-gguf-q5-ks", + "size": 6483592288 + }, + { + "id": "gemma2:9b-gguf-q5-km", + "size": 6647366752 + }, + { + "id": "gemma2:2b-gguf-q5-km", + "size": 1923278592 + }, + { + "id": "gemma2:27b-gguf-q2-k", + "size": 10449575584 + }, + { + "id": "gemma2:onnx", + "size": 1708582496 + }, + { + "id": "gemma2:27b-gguf-q3-kl", + "size": 14519361184 + }, + { + "id": "gemma2:9b-gguf-q6-k", + "size": 7589069920 + }, + { + "id": "gemma2:27b-gguf-q3-ks", + "size": 12169060000 + }, + { + "id": "gemma2:27b-gguf-q3-km", + "size": 13424647840 + }, + { + "id": "gemma2:9b-gguf-q4-ks", + "size": 5478925408 + }, + { + "id": "gemma2:27b-gguf-q4-km", + "size": 16645381792 + }, + { + "id": "gemma2:9b-gguf-q3-km", + "size": 4761781344 + }, + { + "id": "gemma2:9b-gguf-q3-kl", + "size": 5132452960 + }, + { + "id": "gemma2:27b-gguf-q5-ks", + "size": 18884206240 + }, + { + "id": "gemma2:2b-gguf-q3-kl", + "size": 1550436096 + }, + { + "id": "gemma2:9b-gguf-q2-k", + "size": 3805398112 + }, + { + "id": "gemma2:2b-gguf", + "size": 1708582496 + }, + { + "id": "gemma2:27b-gguf-q5-km", + "size": 19408117408 + }, + { + "id": "gemma2:2b-gguf-q2-k", + "size": 1229829888 + }, + { + "id": "gemma2:27b-gguf-q6-k", + "size": 22343524000 + }, + { + "id": "gemma2:2b-gguf-q5-ks", + "size": 1882543872 + }, + { + "id": "gemma2:9b-gguf-q8-0", + "size": 9827148896 + }, + { + "id": "gemma2:27b-gguf-q8-0", + "size": 28937387680 + }, + { + "id": "gemma2:27b-gguf-q4-ks", + "size": 15739264672 + } + ] + }, + { + "id": "cortexso/aya", + "metadata": { + "_id": "66790e21db26e8589ccd3816", + "author": "cortexso", + "cardData": { + "license": "apache-2.0" + }, + "createdAt": "2024-06-24T06:11:45.000Z", + "description": "---\nlicense: apache-2.0\n---\n\n## Overview\n\nThe Aya model is a massively multilingual generative language model that follows instructions in 101 languages.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [12.9b-gguf](https://huggingface.co/cortexhub/aya/tree/12.9b-gguf) | `cortex run aya:12.9b-gguf` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexhub/aya\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run aya\n ```\n\n## Credits\n\n- **Author:** [Cohere For AI](https://cohere.for.ai)\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)", + "disabled": false, + "downloads": 11, + "gated": false, + "id": "cortexso/aya", + "inference": "library-not-detected", + "lastModified": "2024-11-12T20:24:22.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/aya", + "private": false, + "sha": "cae2291fec1dc73739fb8189f9165d23ebe398b8", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:apache-2.0", "region:us"], + "usedStorage": 21527051168 + }, + "models": [ + { + "id": "aya:12.9b-gguf", + "size": 21527051168 + }, + { + "id": "aya:gguf", + "size": 21527051168 + } + ] + }, + { + "id": "cortexso/qwen2.5", + "metadata": { + "_id": "671d0d55748faf685e6450a3", + "author": "cortexso", + "cardData": { + "license": "apache-2.0" + }, + "createdAt": "2024-10-26T15:40:05.000Z", + "description": "---\nlicense: apache-2.0\n---\n\n## Overview\n\nQwen2.5 by Qwen is a family of model include various specialized models for coding and mathematics available in multiple sizes from 0.5B to 72B parameters\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [main/default](https://huggingface.co/cortexso/qwen2.5/tree/main) | `cortex run qwen2.5` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexso/qwen2.5\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run qwen2.5\n ```\n\n## Credits\n\n- **Author:** Qwen\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)\n- **Papers:** [Qwen2.5 Blog](https://qwenlm.github.io/blog/qwen2.5/)", + "disabled": false, + "downloads": 17, + "gated": false, + "id": "cortexso/qwen2.5", + "inference": "library-not-detected", + "lastModified": "2024-10-28T12:59:17.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/qwen2.5", + "private": false, + "sha": "3b0b7a4bca6aada4c97cc7d8133a8adb11b025fa", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:apache-2.0", "region:us"], + "usedStorage": 733469812928 + }, + "models": [ + { + "id": "qwen2.5:7b-gguf-q2-k", + "size": 3015940416 + }, + { + "id": "qwen2.5:7b-gguf-q3-ks", + "size": 3492368704 + }, + { + "id": "qwen2.5:7b-gguf-q3-km", + "size": 3808391488 + }, + { + "id": "qwen2.5:7b-gguf-q3-kl", + "size": 4088459584 + }, + { + "id": "qwen2.5:7b-gguf-q4-km", + "size": 4683073856 + }, + { + "id": "qwen2.5:7b-gguf-q5-ks", + "size": 5315176768 + }, + { + "id": "qwen2.5:7b-gguf-q5-km", + "size": 5444831552 + }, + { + "id": "qwen2.5:7b-gguf-q6-k", + "size": 6254199104 + }, + { + "id": "qwen2.5:0.5b-gguf-q3-km", + "size": 355466432 + }, + { + "id": "qwen2.5:0.5b-gguf-q3-kl", + "size": 369358016 + }, + { + "id": "qwen2.5:1.5b-gguf-q2-k", + "size": 676304768 + }, + { + "id": "qwen2.5:0.5b-gguf-q5-km", + "size": 420085952 + }, + { + "id": "qwen2.5:7b-gguf-q8-0", + "size": 8098525504 + }, + { + "id": "qwen2.5:1.5b-gguf-q3-kl", + "size": 880162688 + }, + { + "id": "qwen2.5:1.5b-gguf-q4-km", + "size": 986048384 + }, + { + "id": "qwen2.5:1.5b-gguf-q8-0", + "size": 1646572928 + }, + { + "id": "qwen2.5:1.5b-gguf-q5-km", + "size": 1125050240 + }, + { + "id": "qwen2.5:3b-gguf-q3-km", + "size": 1590475584 + }, + { + "id": "qwen2.5:3b-gguf-q4-km", + "size": 1929902912 + }, + { + "id": "qwen2.5:3b-gguf-q5-ks", + "size": 2169666368 + }, + { + "id": "qwen2.5:1.5b-gguf-q4-ks", + "size": 940312448 + }, + { + "id": "qwen2.5:14b-gguf-q4-km", + "size": 8988110592 + }, + { + "id": "qwen2.5:3b-gguf-q6-k", + "size": 2538158912 + }, + { + "id": "qwen2.5:14b-gguf-q3-kl", + "size": 7924768512 + }, + { + "id": "qwen2.5:coder-7b-gguf-q6-k", + "size": 6254199168 + }, + { + "id": "qwen2.5:14b-gguf-q5-ks", + "size": 10266554112 + }, + { + "id": "qwen2.5:14b-gguf-q5-km", + "size": 10508873472 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q2-k", + "size": 676304864 + }, + { + "id": "qwen2.5:14b-gguf-q6-k", + "size": 12124684032 + }, + { + "id": "qwen2.5:14b-gguf-q8-0", + "size": 15701597952 + }, + { + "id": "qwen2.5:32b-gguf-q2-k", + "size": 12313098752 + }, + { + "id": "qwen2.5:32b-gguf-q3-km", + "size": 15935048192 + }, + { + "id": "qwen2.5:32b-gguf-q3-kl", + "size": 17247078912 + }, + { + "id": "qwen2.5:32b-gguf-q4-ks", + "size": 18784410112 + }, + { + "id": "qwen2.5:32b-gguf-q5-ks", + "size": 22638254592 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q5-km", + "size": 1125050336 + }, + { + "id": "qwen2.5:72b-gguf-q2-k", + "size": 29811762464 + }, + { + "id": "qwen2.5:math-7b-gguf-q3-ks", + "size": 3492368704 + }, + { + "id": "qwen2.5:72b-gguf-q3-ks", + "size": 34487788832 + }, + { + "id": "qwen2.5:32b-gguf-q4-km", + "size": 19851336192 + }, + { + "id": "qwen2.5:math-7b-gguf-q3-kl", + "size": 4088459584 + }, + { + "id": "qwen2.5:0.5b-gguf-q4-km", + "size": 397807808 + }, + { + "id": "qwen2.5:3b-gguf-q2-k", + "size": 1274755904 + }, + { + "id": "qwen2.5:0.5b-gguf-q6-k", + "size": 505736384 + }, + { + "id": "qwen2.5:1.5b-gguf-q3-ks", + "size": 760944512 + }, + { + "id": "qwen2.5:72b-gguf-q3-kl", + "size": 39505224992 + }, + { + "id": "qwen2.5:coder-7b-gguf-q2-k", + "size": 3015940480 + }, + { + "id": "qwen2.5:14b-gguf-q2-k", + "size": 5770497792 + }, + { + "id": "qwen2.5:32b-gguf-q3-ks", + "size": 14392330752 + }, + { + "id": "qwen2.5:coder-7b-gguf-q3-ks", + "size": 3492368768 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q6-k", + "size": 1272739808 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q3-km", + "size": 824178592 + }, + { + "id": "qwen2.5:math-7b-gguf-q6-k", + "size": 6254199104 + }, + { + "id": "qwen2.5:coder-7b-gguf-q3-km", + "size": 3808391552 + }, + { + "id": "qwen2.5:coder-7b-gguf-q3-kl", + "size": 4088459648 + }, + { + "id": "qwen2.5:coder-7b-gguf-q4-ks", + "size": 4457769344 + }, + { + "id": "qwen2.5:coder-7b-gguf-q8-0", + "size": 8098525568 + }, + { + "id": "qwen2.5:32b-gguf-q5-km", + "size": 23262157312 + }, + { + "id": "qwen2.5:72b-gguf-q3-km", + "size": 37698725152 + }, + { + "id": "qwen2.5:math-7b-gguf-q3-km", + "size": 3808391488 + }, + { + "id": "qwen2.5:0.5b-gguf-q3-ks", + "size": 338263232 + }, + { + "id": "qwen2.5:coder-7b-gguf-q5-km", + "size": 5444831616 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q3-km", + "size": 824178656 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q3-kl", + "size": 880162784 + }, + { + "id": "qwen2.5:72b-gguf-q4-km", + "size": 47415715104 + }, + { + "id": "qwen2.5:3b-gguf-q4-ks", + "size": 1834384192 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q4-ks", + "size": 940312544 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q5-ks", + "size": 1098729440 + }, + { + "id": "qwen2.5:3b-gguf-q3-kl", + "size": 1707391808 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q6-k", + "size": 1272739744 + }, + { + "id": "qwen2.5:32b-gguf-q8-0", + "size": 34820884992 + }, + { + "id": "qwen2.5:1.5b-gguf-q6-k", + "size": 1272739712 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q8-0", + "size": 1646573024 + }, + { + "id": "qwen2.5:math-7b-gguf-q4-km", + "size": 4683073856 + }, + { + "id": "qwen2.5:0.5b-gguf-q8-0", + "size": 531068096 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q3-ks", + "size": 760944544 + }, + { + "id": "qwen2.5:72b-gguf-q4-ks", + "size": 43889222944 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q4-ks", + "size": 940312480 + }, + { + "id": "qwen2.5:math-7b-gguf-q5-ks", + "size": 5315176768 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q5-km", + "size": 1125050272 + }, + { + "id": "qwen2.5:0.5b-gguf-q5-ks", + "size": 412710080 + }, + { + "id": "qwen2.5:3b-gguf-q3-ks", + "size": 1454357312 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q2-k", + "size": 676304800 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q3-ks", + "size": 760944608 + }, + { + "id": "qwen2.5:3b-gguf-q5-km", + "size": 2224814912 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q8-0", + "size": 1646572960 + }, + { + "id": "qwen2.5:0.5b-gguf-q2-k", + "size": 338607296 + }, + { + "id": "qwen2.5:14b-gguf-q3-ks", + "size": 6659596032 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q4-km", + "size": 986048416 + }, + { + "id": "qwen2.5:1.5b-gguf-q3-km", + "size": 824178560 + }, + { + "id": "qwen2.5:7b-gguf-q4-ks", + "size": 4457769280 + }, + { + "id": "qwen2.5:1.5b-gguf-q5-ks", + "size": 1098729344 + }, + { + "id": "qwen2.5:coder-1.5b-gguf-q4-km", + "size": 986048480 + }, + { + "id": "qwen2.5:math-7b-gguf-q2-k", + "size": 3015940416 + }, + { + "id": "qwen2.5:math-7b-gguf-q5-km", + "size": 5444831552 + }, + { + "id": "qwen2.5:0.5b-gguf-q4-ks", + "size": 385471680 + }, + { + "id": "qwen2.5:coder-7b-gguf-q5-ks", + "size": 5315176832 + }, + { + "id": "qwen2.5:math-7b-gguf-q4-ks", + "size": 4457769280 + }, + { + "id": "qwen2.5:math-7b-gguf-q8-0", + "size": 8098525504 + }, + { + "id": "qwen2.5:3b-gguf-q8-0", + "size": 3285476160 + }, + { + "id": "qwen2.5:14b-gguf-q3-km", + "size": 7339204352 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q3-kl", + "size": 880162720 + }, + { + "id": "qwen2.5:32b-gguf-q6-k", + "size": 26886154752 + }, + { + "id": "qwen2.5:math-1.5b-gguf-q5-ks", + "size": 1098729376 + }, + { + "id": "qwen2.5:coder-7b-gguf-q4-km", + "size": 4683073920 + } + ] + }, + { + "id": "cortexso/llama3.2", + "metadata": { + "_id": "66f63309ba963b1db95deaa4", + "author": "cortexso", + "cardData": { + "license": "llama3.2" + }, + "createdAt": "2024-09-27T04:22:33.000Z", + "description": "---\nlicense: llama3.2\n---\n\n## Overview\n\nMeta developed and released the [Meta Llama 3.2](https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct) family of large language models (LLMs), a collection of pretrained and instruction tuned generative text models in 1B and 3B sizes (text in/text out). The Llama 3.2 instruction-tuned text only models are optimized for multilingual dialogue use cases, including agentic retrieval and summarization tasks. They outperform many of the available open source and closed chat models on common industry benchmarks.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 2 | [gguf](https://huggingface.co/cortexso/llama3.2/tree/gguf) | `cortex run llama3.2:gguf` |\n| 3 | [main/default](https://huggingface.co/cortexso/llama3.2/tree/main) | `cortex run llama3.2` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexso/llama3.2\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run llama3.2\n ```\n\n## Credits\n\n- **Author:** Meta\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct/blob/main/LICENSE.txt)\n- **Papers:** [Llama-3.2 Blog](https://ai.meta.com/blog/llama-3-2-connect-2024-vision-edge-mobile-devices/)", + "disabled": false, + "downloads": 422, + "gated": false, + "id": "cortexso/llama3.2", + "inference": "library-not-detected", + "lastModified": "2024-10-07T06:42:49.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/llama3.2", + "private": false, + "sha": "97784eeed591168e27671d7dd0f8ea68d2e0430c", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:llama3.2", "region:us"], + "usedStorage": 21014285888 + }, + "models": [ + { + "id": "llama3.2:3b-gguf-q3-ks", + "size": 1542848672 + }, + { + "id": "llama3.2:3b-gguf-q3-kl", + "size": 1815347360 + }, + { + "id": "llama3.2:3b-gguf-q3-km", + "size": 1687158944 + }, + { + "id": "llama3.2:3b-gguf-q4-ks", + "size": 1928200352 + }, + { + "id": "llama3.2:3b-gguf-q5-ks", + "size": 2269511840 + }, + { + "id": "llama3.2:3b-gguf-q4-km", + "size": 2019377312 + }, + { + "id": "llama3.2:3b-gguf-q6-k", + "size": 2643853472 + }, + { + "id": "llama3.2:3b-gguf-q2-k", + "size": 1363935392 + }, + { + "id": "llama3.2:3b-gguf-q5-km", + "size": 2322153632 + }, + { + "id": "llama3.2:3b-gguf-q8-0", + "size": 3421898912 + } + ] + }, + { + "id": "cortexso/deepseek-r1-distill-qwen-1.5b", + "metadata": { + "_id": "678e84d99d66241aabee008a", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-20T17:16:09.000Z", + "description": "---\nlicense: mit\n---\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Qwen 1.5B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B) model, a distilled version of the Qwen 1.5B language model. It is fine-tuned for high-performance text generation and optimized for dialogue and information-seeking tasks. This model achieves a balance of efficiency and accuracy while maintaining a smaller footprint compared to the original Qwen 1.5B.\n\nThe model is designed for applications in customer support, conversational AI, and research, prioritizing both helpfulness and safety.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-qwen-1.5b/tree/main) | `cortex run deepseek-r1-distill-qwen-1.5b` |\n\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```text\n cortexso/deepseek-r1-distill-qwen-1.5b\n ```\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-qwen-1.5b\n ```\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)", + "disabled": false, + "downloads": 70, + "gated": false, + "id": "cortexso/deepseek-r1-distill-qwen-1.5b", + "inference": "library-not-detected", + "lastModified": "2025-01-24T04:26:48.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-qwen-1.5b", + "private": false, + "sha": "15c639a690dc821d63b82f1b3a0c2b9051411d23", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 11611279040 + }, + "models": [ + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q3-ks", + "size": 861221600 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q3-km", + "size": 924455648 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q3-kl", + "size": 980439776 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q4-ks", + "size": 1071584480 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q6-k", + "size": 1464178400 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q5-ks", + "size": 1259173088 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q2-k", + "size": 752879840 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q5-km", + "size": 1285493984 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q4-km", + "size": 1117320416 + }, + { + "id": "deepseek-r1-distill-qwen-1.5b:1.5b-gguf-q8-0", + "size": 1894531808 + } + ] + }, + { + "id": "cortexso/deepseek-r1-distill-qwen-32b", + "metadata": { + "_id": "678fe132df84bd3d94f37e58", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-21T18:02:26.000Z", + "description": "---\nlicense: mit\n---\n\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Qwen 32B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B) model, a distilled version of the Qwen 32B language model. This is the most advanced and largest model in the DeepSeek R1 Distill family, offering unparalleled performance in text generation, dialogue optimization, and reasoning tasks. \n\nThe model is tailored for large-scale applications in conversational AI, research, enterprise solutions, and knowledge systems, delivering exceptional accuracy, efficiency, and safety at scale.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-qwen-32b/tree/main) | `cortex run deepseek-r1-distill-qwen-32b` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```text\n cortexso/deepseek-r1-distill-qwen-32b\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-qwen-32b\n ```\n\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)\n", + "disabled": false, + "downloads": 6, + "gated": false, + "id": "cortexso/deepseek-r1-distill-qwen-32b", + "inference": "library-not-detected", + "lastModified": "2025-01-23T08:50:04.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-qwen-32b", + "private": false, + "sha": "a5d2268c4d8bc697597d562172490d3e21059fc4", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 206130747200 + }, + "models": [ + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q2-k", + "size": 12313098016 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q3-ks", + "size": 14392330016 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q3-kl", + "size": 17247078176 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q4-ks", + "size": 18784409376 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q4-km", + "size": 19851335456 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q5-km", + "size": 23262156576 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q3-km", + "size": 15935047456 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q6-k", + "size": 26886154016 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q8-0", + "size": 34820884256 + }, + { + "id": "deepseek-r1-distill-qwen-32b:32b-gguf-q5-ks", + "size": 22638253856 + } + ] + }, + { + "id": "cortexso/deepseek-r1-distill-llama-8b", + "metadata": { + "_id": "678f4b5625a9b93997f1f666", + "author": "cortexso", + "cardData": { + "license": "mit" + }, + "createdAt": "2025-01-21T07:23:02.000Z", + "description": "---\nlicense: mit\n---\n\n## Overview\n\n**DeepSeek** developed and released the [DeepSeek R1 Distill Llama 8B](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B) model, a distilled version of the Llama 8B language model. This variant is fine-tuned for high-performance text generation, optimized for dialogue, and tailored for information-seeking tasks. It offers a robust balance between model size and performance, making it suitable for demanding conversational AI and research use cases.\n\nThe model is designed to deliver accurate, efficient, and safe responses in applications such as customer support, knowledge systems, and research environments.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 1 | [gguf](https://huggingface.co/cortexso/deepseek-r1-distill-llama-8b/tree/main) | `cortex run deepseek-r1-distill-llama-8b` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```bash\n cortexso/deepseek-r1-distill-llama-8b\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```bash\n cortex run deepseek-r1-distill-llama-8b\n ```\n\n## Credits\n\n- **Author:** DeepSeek\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-8B#7-license)\n- **Papers:** [DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/html/2501.12948v1)\n", + "disabled": false, + "downloads": 59, + "gated": false, + "id": "cortexso/deepseek-r1-distill-llama-8b", + "inference": "library-not-detected", + "lastModified": "2025-01-23T08:46:41.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/deepseek-r1-distill-llama-8b", + "private": false, + "sha": "f69bd2c9e2ea1380cbcaeec136ab71a4b164b200", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:mit", "region:us"], + "usedStorage": 51266986688 + }, + "models": [ + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q4-ks", + "size": 4692670944 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q3-ks", + "size": 3664501216 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q3-km", + "size": 4018919904 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q3-kl", + "size": 4321958368 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q4-km", + "size": 4920736224 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q2-k", + "size": 3179133408 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q8-0", + "size": 8540772832 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q5-ks", + "size": 5599295968 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q5-km", + "size": 5732989408 + }, + { + "id": "deepseek-r1-distill-llama-8b:8b-gguf-q6-k", + "size": 6596008416 + } + ] + }, + { + "id": "cortexso/llama3.1", + "metadata": { + "_id": "66a76e01a1037fe261a5a472", + "author": "cortexso", + "cardData": { + "license": "llama3.1" + }, + "createdAt": "2024-07-29T10:25:05.000Z", + "description": "---\nlicense: llama3.1\n---\n\n## Overview\n\nMeta developed and released the [Meta Llama 3.1](https://huggingface.co/meta-llama/Meta-Llama-3.1-8B) family of large language models (LLMs), a collection of pretrained and instruction tuned generative text models in 8 and 70B sizes. The Llama 3 instruction tuned models are optimized for dialogue use cases and outperform many of the available open source chat models on common industry benchmarks. Further, in developing these models, we took great care to optimize helpfulness and safety.\n\n## Variants\n\n| No | Variant | Cortex CLI command |\n| --- | --- | --- |\n| 2 | [gguf](https://huggingface.co/cortexso/llama3.1/tree/gguf) | `cortex run llama3.1:gguf` |\n| 3 | [main/default](https://huggingface.co/cortexso/llama3.1/tree/main) | `cortex run llama3.1` |\n\n## Use it with Jan (UI)\n\n1. Install **Jan** using [Quickstart](https://jan.ai/docs/quickstart)\n2. Use in Jan model Hub:\n ```\n cortexso/llama3.1\n ```\n\n## Use it with Cortex (CLI)\n\n1. Install **Cortex** using [Quickstart](https://cortex.jan.ai/docs/quickstart)\n2. Run the model with command:\n ```\n cortex run llama3.1\n ```\n\n## Credits\n\n- **Author:** Meta\n- **Converter:** [Homebrew](https://www.homebrew.ltd/)\n- **Original License:** [License](https://huggingface.co/meta-llama/Meta-Llama-3.1-8B/blob/main/LICENSE)\n- **Papers:** [Llama-3.1 Blog](https://scontent.fsgn3-1.fna.fbcdn.net/v/t39.2365-6/452387774_1036916434819166_4173978747091533306_n.pdf?_nc_cat=104&ccb=1-7&_nc_sid=3c67a6&_nc_ohc=DTS7hDTcxZoQ7kNvgHxaQ8K&_nc_ht=scontent.fsgn3-1.fna&oh=00_AYC1gXduoxatzt8eFMfLunrRUzpzQcoKzAktIOT7FieZAQ&oe=66AE9C4D)", + "disabled": false, + "downloads": 29, + "gated": false, + "id": "cortexso/llama3.1", + "inference": "library-not-detected", + "lastModified": "2024-11-12T20:11:22.000Z", + "likes": 0, + "model-index": null, + "modelId": "cortexso/llama3.1", + "private": false, + "sha": "4702595a4e5e5aba5c0f7d1180199cecc076597d", + "siblings": [ + { + "rfilename": ".gitattributes" + }, + { + "rfilename": "README.md" + }, + { + "rfilename": "metadata.yml" + }, + { + "rfilename": "model.yml" + } + ], + "spaces": [], + "tags": ["license:llama3.1", "region:us"], + "usedStorage": 175802939712 + }, + "models": [ + { + "id": "llama3.1:8b-gguf-q3-ks", + "size": 3664504064 + }, + { + "id": "llama3.1:8b-gguf-q8-0", + "size": 8540775680 + }, + { + "id": "llama3.1:8b-gguf-q4-ks", + "size": 4692673792 + }, + { + "id": "llama3.1:8b-gguf-q3-km", + "size": 4018922752 + }, + { + "id": "llama3.1:8b-gguf", + "size": 4920734656 + }, + { + "id": "llama3.1:8b-gguf-q3-kl", + "size": 4321961216 + }, + { + "id": "llama3.1:8b-gguf-q4-km", + "size": 4920739072 + }, + { + "id": "llama3.1:8b-gguf-q5-km", + "size": 5732992256 + }, + { + "id": "llama3.1:8b-gguf-q6-k", + "size": 6596011264 + }, + { + "id": "llama3.1:8b-gguf-q5-ks", + "size": 5599298816 + }, + { + "id": "llama3.1:8b-gguf-q2-k", + "size": 3179136256 + }, + { + "id": "llama3.1:gguf", + "size": 4920734656 + } + ] + } +] diff --git a/extensions/model-extension/rolldown.config.mjs b/extensions/model-extension/rolldown.config.mjs index 0e4c866fe2..01672adddd 100644 --- a/extensions/model-extension/rolldown.config.mjs +++ b/extensions/model-extension/rolldown.config.mjs @@ -1,5 +1,6 @@ import { defineConfig } from 'rolldown' import settingJson from './resources/settings.json' with { type: 'json' } +import modelSources from './resources/default.json' with { type: 'json' } export default defineConfig({ input: 'src/index.ts', @@ -12,5 +13,6 @@ export default defineConfig({ SETTINGS: JSON.stringify(settingJson), API_URL: JSON.stringify('http://127.0.0.1:39291'), SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), + DEFAULT_MODEL_SOURCES: JSON.stringify(modelSources), }, }) diff --git a/extensions/model-extension/src/@types/global.d.ts b/extensions/model-extension/src/@types/global.d.ts index 92d9df09b0..1b6b71a03f 100644 --- a/extensions/model-extension/src/@types/global.d.ts +++ b/extensions/model-extension/src/@types/global.d.ts @@ -2,6 +2,7 @@ declare const NODE: string declare const API_URL: string declare const SOCKET_URL: string declare const SETTINGS: SettingComponentProps[] +declare const DEFAULT_MODEL_SOURCES: any interface Core { api: APIFunctions diff --git a/extensions/model-extension/src/cortex.ts b/extensions/model-extension/src/cortex.ts index 7618e8170a..d7d4a02334 100644 --- a/extensions/model-extension/src/cortex.ts +++ b/extensions/model-extension/src/cortex.ts @@ -1,6 +1,6 @@ import PQueue from 'p-queue' import ky from 'ky' -import { extractModelLoadParams, Model } from '@janhq/core' +import { extractModelLoadParams, Model, ModelSource } from '@janhq/core' import { extractInferenceParams } from '@janhq/core' /** * cortex.cpp Model APIs interface @@ -19,9 +19,12 @@ interface ICortexAPI { updateModel(model: object): Promise cancelModelPull(model: string): Promise configs(body: { [key: string]: any }): Promise + getSources(): Promise + addSource(source: string): Promise + deleteSource(source: string): Promise } -type ModelList = { +type Data = { data: any[] } @@ -53,7 +56,7 @@ export class CortexAPI implements ICortexAPI { */ getModels(): Promise { return this.queue - .add(() => ky.get(`${API_URL}/v1/models?limit=-1`).json()) + .add(() => ky.get(`${API_URL}/v1/models?limit=-1`).json()) .then((e) => typeof e === 'object' ? e.data.map((e) => this.transformModel(e)) : [] ) @@ -148,6 +151,47 @@ export class CortexAPI implements ICortexAPI { .catch(() => false) } + // BEGIN - Model Sources + /** + * Get model sources + * @param model + */ + async getSources(): Promise { + return this.queue + .add(() => ky.get(`${API_URL}/v1/models/sources`).json()) + .then((e) => (typeof e === 'object' ? (e.data as ModelSource[]) : [])) + .catch(() => []) + } + + /** + * Add a model source + * @param model + */ + async addSource(source: string): Promise { + return this.queue.add(() => + ky.post(`${API_URL}/v1/models/sources`, { + json: { + source, + }, + }) + ) + } + + /** + * Delete a model source + * @param model + */ + async deleteSource(source: string): Promise { + return this.queue.add(() => + ky.delete(`${API_URL}/v1/models/sources`, { + json: { + source, + }, + }) + ) + } + // END - Model Sources + /** * Do health check on cortex.cpp * @returns diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index 832f279f9d..719671cfda 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -11,6 +11,7 @@ import { events, DownloadEvent, OptionType, + ModelSource, } from '@janhq/core' import { CortexAPI } from './cortex' import { scanModelsFolder } from './legacy/model-json' @@ -243,6 +244,35 @@ export default class JanModelExtension extends ModelExtension { return this.cortexAPI.importModel(model, modelPath, name, option) } + // BEGIN - Model Sources + /** + * Get model sources + * @param model + */ + async getSources(): Promise { + const sources = await this.cortexAPI.getSources() + return sources.concat( + DEFAULT_MODEL_SOURCES.filter((e) => !sources.some((x) => x.id === e.id)) + ) + } + + /** + * Add a model source + * @param model + */ + async addSource(source: string): Promise { + return this.cortexAPI.addSource(source) + } + + /** + * Delete a model source + * @param model + */ + async deleteSource(source: string): Promise { + return this.cortexAPI.deleteSource(source) + } + // END - Model Sources + /** * Check model status * @param model diff --git a/joi/package.json b/joi/package.json index 69bdb243e9..2f7d771d57 100644 --- a/joi/package.json +++ b/joi/package.json @@ -30,6 +30,7 @@ "dependencies": { "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", diff --git a/joi/src/core/Dropdown/index.tsx b/joi/src/core/Dropdown/index.tsx new file mode 100644 index 0000000000..6d9abcbea3 --- /dev/null +++ b/joi/src/core/Dropdown/index.tsx @@ -0,0 +1,45 @@ +import React, { Fragment, PropsWithChildren, ReactNode } from 'react' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import './styles.scss' +import { twMerge } from 'tailwind-merge' + +type Props = { + options?: { name: ReactNode; value: string; suffix?: ReactNode }[] + className?: string + onValueChanged?: (value: string) => void +} + +const Dropdown = (props: PropsWithChildren & Props) => { + return ( + + {props.children} + + + + {props.options?.map((e, i) => ( + + {i !== 0 && ( + + )} + props.onValueChanged?.(e.value)} + > + {e.name} +
+ {e.suffix} + + + ))} + + + + + ) +} + +export { Dropdown } diff --git a/joi/src/core/Dropdown/styles.scss b/joi/src/core/Dropdown/styles.scss new file mode 100644 index 0000000000..1318f83b71 --- /dev/null +++ b/joi/src/core/Dropdown/styles.scss @@ -0,0 +1,154 @@ +.DropdownMenuContent, +.DropdownMenuSubContent { + min-width: 220px; + background-color: white; + border-radius: 6px; + overflow: hidden; + padding: 0px; + box-shadow: + 0px 10px 38px -10px rgba(22, 23, 24, 0.35), + 0px 10px 20px -15px rgba(22, 23, 24, 0.2); + animation-duration: 400ms; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + will-change: transform, opacity; +} +.DropdownMenuContent[data-side='top'], +.DropdownMenuSubContent[data-side='top'] { + animation-name: slideDownAndFade; +} +.DropdownMenuContent[data-side='right'], +.DropdownMenuSubContent[data-side='right'] { + animation-name: slideLeftAndFade; +} +.DropdownMenuContent[data-side='bottom'], +.DropdownMenuSubContent[data-side='bottom'] { + animation-name: slideUpAndFade; +} +.DropdownMenuContent[data-side='left'], +.DropdownMenuSubContent[data-side='left'] { + animation-name: slideRightAndFade; +} + +.DropdownMenuItem { + padding: 14px; + cursor: pointer; + outline: none; + flex: 1; + display: flex; + justify-content: space-between; /* Distribute space between children */ + align-items: center; /* Optional: Align items vertically */ + gap: 16px; +} +.DropdownMenuCheckboxItem, +.DropdownMenuRadioItem, +.DropdownMenuSubTrigger { + font-size: 13px; + line-height: 1; + border-radius: 3px; + display: flex; + align-items: center; + height: 25px; + padding: 0 0; + position: relative; + padding-left: 25px; + user-select: none; + outline: none; +} +.DropdownMenuItem[data-disabled], +.DropdownMenuCheckboxItem[data-disabled], +.DropdownMenuRadioItem[data-disabled], +.DropdownMenuSubTrigger[data-disabled] { + pointer-events: none; +} +.DropdownMenuItem[data-highlighted], +.DropdownMenuCheckboxItem[data-highlighted], +.DropdownMenuRadioItem[data-highlighted], +.DropdownMenuSubTrigger[data-highlighted] { + background-color: hsla(var(--secondary-bg)); +} + +.DropdownMenuSeparator { + height: 1px; + width: '100%'; + background-color: hsla(var(--secondary-bg)); +} + +.DropdownMenuItem::hover { + background-color: hsla(var(--secondary-bg)); +} + +.DropdownMenuLabel { + padding-left: 25px; + font-size: 12px; + line-height: 25px; + color: var(--mauve-11); +} + +.DropdownMenuItemIndicator { + position: absolute; + left: 0; + width: 25px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.DropdownMenuArrow { + fill: white; +} + +.RightSlot { + margin-left: auto; + padding-left: 20px; + color: var(--mauve-11); +} +[data-highlighted] > .RightSlot { + color: white; +} +[data-disabled] .RightSlot { + color: var(--mauve-8); +} + +@keyframes slideUpAndFade { + from { + opacity: 0; + transform: translateY(2px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideRightAndFade { + from { + opacity: 0; + transform: translateX(-2px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideDownAndFade { + from { + opacity: 0; + transform: translateY(-2px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideLeftAndFade { + from { + opacity: 0; + transform: translateX(2px); + } + to { + opacity: 1; + transform: translateX(0); + } +} diff --git a/joi/src/core/Select/index.tsx b/joi/src/core/Select/index.tsx index bce5473da1..a8c3df5289 100644 --- a/joi/src/core/Select/index.tsx +++ b/joi/src/core/Select/index.tsx @@ -1,11 +1,7 @@ -import React, { ReactNode } from 'react' +import React from 'react' import * as SelectPrimitive from '@radix-ui/react-select' -import { - CheckIcon, - ChevronDownIcon, - ChevronUpIcon, -} from '@radix-ui/react-icons' +import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons' import './styles.scss' import { twMerge } from 'tailwind-merge' diff --git a/joi/src/index.test.ts b/joi/src/index.test.ts index 8bfba8d933..7937ea1382 100644 --- a/joi/src/index.test.ts +++ b/joi/src/index.test.ts @@ -15,6 +15,7 @@ jest.mock('./core/Select/styles.scss', () => ({})) jest.mock('./core/TextArea/styles.scss', () => ({})) jest.mock('./core/Tabs/styles.scss', () => ({})) jest.mock('./core/Accordion/styles.scss', () => ({})) +jest.mock('./core/Dropdown/styles.scss', () => ({})) describe('Exports', () => { it('exports all components and hooks', () => { diff --git a/joi/src/index.ts b/joi/src/index.ts index 5431627475..ebeb3ac4dc 100644 --- a/joi/src/index.ts +++ b/joi/src/index.ts @@ -12,6 +12,7 @@ export * from './core/Select' export * from './core/TextArea' export * from './core/Tabs' export * from './core/Accordion' +export * from './core/Dropdown' export * from './hooks/useClipboard' export * from './hooks/usePageLeave' diff --git a/web/containers/Layout/BottomPanel/SystemMonitor/TableActiveModel/index.tsx b/web/containers/Layout/BottomPanel/SystemMonitor/TableActiveModel/index.tsx index 7a7dd1e241..e47296ffc8 100644 --- a/web/containers/Layout/BottomPanel/SystemMonitor/TableActiveModel/index.tsx +++ b/web/containers/Layout/BottomPanel/SystemMonitor/TableActiveModel/index.tsx @@ -6,7 +6,7 @@ import { useActiveModel } from '@/hooks/useActiveModel' import { useGetEngines } from '@/hooks/useEngineManagement' -import { toGibibytes } from '@/utils/converter' +import { toGigabytes } from '@/utils/converter' import { isLocalEngine } from '@/utils/modelEngine' @@ -34,7 +34,7 @@ const TableActiveModel = () => { {activeModel.metadata?.size - ? toGibibytes(activeModel.metadata?.size) + ? toGigabytes(activeModel.metadata?.size) : '-'} diff --git a/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx b/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx index 3dfdff2f9e..f47dfaeb73 100644 --- a/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx +++ b/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx @@ -16,7 +16,7 @@ import useGetSystemResources from '@/hooks/useGetSystemResources' import { usePath } from '@/hooks/usePath' -import { toGibibytes } from '@/utils/converter' +import { toGigabytes } from '@/utils/converter' import { utilizedMemory } from '@/utils/memory' @@ -134,8 +134,8 @@ const SystemMonitor = () => {
Memory
- {toGibibytes(usedRam, { hideUnit: true })}/ - {toGibibytes(totalRam, { hideUnit: true })} GB + {toGigabytes(usedRam, { hideUnit: true })}/ + {toGigabytes(totalRam, { hideUnit: true })} GB
diff --git a/web/containers/Layout/index.tsx b/web/containers/Layout/index.tsx index 29fda70de8..5b17eb4fc7 100644 --- a/web/containers/Layout/index.tsx +++ b/web/containers/Layout/index.tsx @@ -21,7 +21,6 @@ import { SUCCESS_SET_NEW_DESTINATION } from '@/screens/Settings/Advanced/DataFol import CancelModelImportModal from '@/screens/Settings/CancelModelImportModal' import ChooseWhatToImportModal from '@/screens/Settings/ChooseWhatToImportModal' import EditModelInfoModal from '@/screens/Settings/EditModelInfoModal' -import HuggingFaceRepoDetailModal from '@/screens/Settings/HuggingFaceRepoDetailModal' import ImportModelOptionModal from '@/screens/Settings/ImportModelOptionModal' import ImportingModelModal from '@/screens/Settings/ImportingModelModal' import SelectingModelModal from '@/screens/Settings/SelectingModelModal' @@ -148,7 +147,6 @@ const BaseLayout = () => { {importModelStage === 'CONFIRM_CANCEL' && } - {showProductAnalyticPrompt && (
diff --git a/web/containers/Loader/Spinner.tsx b/web/containers/Loader/Spinner.tsx new file mode 100644 index 0000000000..a5803d70fb --- /dev/null +++ b/web/containers/Loader/Spinner.tsx @@ -0,0 +1,53 @@ +import { motion } from 'framer-motion' + +const Spinner = ({ size = 40, strokeWidth = 4 }) => { + const radius = size / 2 - strokeWidth + const circumference = 2 * Math.PI * radius + + return ( + + {/* Static background circle */} + + {/* Smooth animated arc */} + + + ) +} + +export default Spinner diff --git a/web/containers/ModalCancelDownload/index.tsx b/web/containers/ModalCancelDownload/index.tsx index 1826c78a7c..886eb3b686 100644 --- a/web/containers/ModalCancelDownload/index.tsx +++ b/web/containers/ModalCancelDownload/index.tsx @@ -1,7 +1,5 @@ import { useCallback } from 'react' -import { Model } from '@janhq/core' - import { Modal, Button, Progress, ModalClose } from '@janhq/joi' import { useAtomValue, useSetAtom } from 'jotai' @@ -16,22 +14,22 @@ import { import { formatDownloadPercentage } from '@/utils/converter' type Props = { - model: Model + modelId: string isFromList?: boolean } -const ModalCancelDownload = ({ model, isFromList }: Props) => { +const ModalCancelDownload = ({ modelId, isFromList }: Props) => { const { abortModelDownload } = useDownloadModel() const removeDownloadState = useSetAtom(removeDownloadStateAtom) const allDownloadStates = useAtomValue(modelDownloadStateAtom) - const downloadState = allDownloadStates[model.id] + const downloadState = allDownloadStates[modelId] const cancelText = `Cancel ${formatDownloadPercentage(downloadState?.percent ?? 0)}` const onAbortDownloadClick = useCallback(() => { - removeDownloadState(model.id) - abortModelDownload(downloadState?.modelId ?? model.id) - }, [downloadState, abortModelDownload, removeDownloadState, model]) + removeDownloadState(modelId) + abortModelDownload(downloadState?.modelId ?? modelId) + }, [downloadState, abortModelDownload, removeDownloadState, modelId]) return ( { {cancelText} ) : ( - + ) + const downloadingButton = + const downloadedButton = ( + + Use + + } + content="Threads are disabled while the server is running" + /> + ) + return ( + <> + {isDownloading + ? downloadingButton + : isDownloaded + ? downloadedButton + : defaultButton} + + ) +} + +export default ModelDownloadButton diff --git a/web/containers/ModelDropdown/index.tsx b/web/containers/ModelDropdown/index.tsx index 40c6fc931e..6d2cc0b235 100644 --- a/web/containers/ModelDropdown/index.tsx +++ b/web/containers/ModelDropdown/index.tsx @@ -37,7 +37,7 @@ import useRecommendedModel from '@/hooks/useRecommendedModel' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' -import { formatDownloadPercentage, toGibibytes } from '@/utils/converter' +import { formatDownloadPercentage, toGigabytes } from '@/utils/converter' import { manualRecommendationModel } from '@/utils/model' import { getLogoEngine } from '@/utils/modelEngine' @@ -481,13 +481,13 @@ const ModelDropdown = ({ {model.name}

- {toGibibytes(model.metadata?.size)} + {toGigabytes(model.metadata?.size)} {!isDownloading ? (
{!isDownloaded && ( - {toGibibytes(model.metadata?.size)} + {toGigabytes(model.metadata?.size)} )} {!isDownloading && !isDownloaded ? ( diff --git a/web/containers/ModelLabel/ModelLabel.test.tsx b/web/containers/ModelLabel/ModelLabel.test.tsx index 48504ff6a9..ca5cf19dc4 100644 --- a/web/containers/ModelLabel/ModelLabel.test.tsx +++ b/web/containers/ModelLabel/ModelLabel.test.tsx @@ -36,46 +36,6 @@ describe('ModelLabel', () => { jest.clearAllMocks() }) - it('renders NotEnoughMemoryLabel when minimumRamModel is greater than totalRam', async () => { - mockUseAtomValue - .mockReturnValueOnce(0) - .mockReturnValueOnce(0) - .mockReturnValueOnce(0) - mockUseActiveModel.mockReturnValue({ - activeModel: { metadata: { size: 0 } }, - }) - mockUseSettings.mockReturnValue({ settings: { run_mode: 'cpu' } }) - - render() - await waitFor(() => { - expect(screen.getByText('Not enough RAM')).toBeDefined() - }) - }) - - it('renders SlowOnYourDeviceLabel when minimumRamModel is less than totalRam but greater than availableRam', async () => { - mockUseAtomValue - .mockReturnValueOnce(100) - .mockReturnValueOnce(50) - .mockReturnValueOnce(10) - mockUseActiveModel.mockReturnValue({ - activeModel: { metadata: { size: 0 } }, - }) - mockUseSettings.mockReturnValue({ settings: { run_mode: 'cpu' } }) - - const props = { - ...defaultProps, - metadata: { - ...defaultProps.metadata, - size: 50, - }, - } - - render() - await waitFor(() => { - expect(screen.getByText('Slow on your device')).toBeDefined() - }) - }) - it('renders nothing when minimumRamModel is less than availableRam', () => { mockUseAtomValue .mockReturnValueOnce(100) diff --git a/web/containers/ModelLabel/index.tsx b/web/containers/ModelLabel/index.tsx index a6237ada6e..564b7edf85 100644 --- a/web/containers/ModelLabel/index.tsx +++ b/web/containers/ModelLabel/index.tsx @@ -1,7 +1,5 @@ import React from 'react' -import { ModelMetadata } from '@janhq/core' -import { Badge } from '@janhq/joi' import { useAtomValue } from 'jotai' import { useActiveModel } from '@/hooks/useActiveModel' @@ -19,18 +17,11 @@ import { } from '@/helpers/atoms/SystemBar.atom' type Props = { - metadata: ModelMetadata + size?: number compact?: boolean } -const UnsupportedModel = () => { - return ( - - Coming Soon - - ) -} -const ModelLabel = ({ metadata, compact }: Props) => { +const ModelLabel = ({ size, compact }: Props) => { const { activeModel } = useActiveModel() const totalRam = useAtomValue(totalRamAtom) const usedRam = useAtomValue(usedRamAtom) @@ -59,11 +50,7 @@ const ModelLabel = ({ metadata, compact }: Props) => { return null } - return metadata?.tags?.includes('Coming Soon') ? ( - - ) : ( - getLabel(metadata?.size ?? 0) - ) + return getLabel(size ?? 0) } export default React.memo(ModelLabel) diff --git a/web/containers/ModelSearch/index.tsx b/web/containers/ModelSearch/index.tsx index 9c3ca1cb02..aa40f83310 100644 --- a/web/containers/ModelSearch/index.tsx +++ b/web/containers/ModelSearch/index.tsx @@ -1,18 +1,16 @@ -import React, { ChangeEvent, useCallback, useState } from 'react' +import React, { ChangeEvent, useCallback, useState, useRef } from 'react' import { Input } from '@janhq/joi' -import { useSetAtom } from 'jotai' import { SearchIcon } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' - -import { toaster } from '@/containers/Toast' -import { useGetHFRepoData } from '@/hooks/useGetHFRepoData' +import { useDebouncedCallback } from 'use-debounce' import { - importHuggingFaceModelStageAtom, - importingHuggingFaceRepoDataAtom, -} from '@/helpers/atoms/HuggingFace.atom' + useGetModelSources, + useModelSourcesMutation, +} from '@/hooks/useModelSource' + +import Spinner from '../Loader/Spinner' type Props = { onSearchLocal?: (searchText: string) => void @@ -20,37 +18,28 @@ type Props = { const ModelSearch = ({ onSearchLocal }: Props) => { const [searchText, setSearchText] = useState('') - const { getHfRepoData } = useGetHFRepoData() - - const setImportingHuggingFaceRepoData = useSetAtom( - importingHuggingFaceRepoDataAtom - ) - const setImportHuggingFaceModelStage = useSetAtom( - importHuggingFaceModelStageAtom - ) - + const [isSearching, setSearching] = useState(false) + const { mutate } = useGetModelSources() + const { addModelSource } = useModelSourcesMutation() + const inputRef = useRef(null) const debounced = useDebouncedCallback(async () => { if (searchText.indexOf('/') === -1) { // If we don't find / in the text, perform a local search onSearchLocal?.(searchText) return } + // Attempt to search local + onSearchLocal?.(searchText) - try { - const data = await getHfRepoData(searchText) - setImportingHuggingFaceRepoData(data) - setImportHuggingFaceModelStage('REPO_DETAIL') - } catch (err) { - let errMessage = 'Unexpected Error' - if (err instanceof Error) { - errMessage = err.message - } - toaster({ - title: errMessage, - type: 'error', + setSearching(true) + // Attempt to search model source + addModelSource(searchText) + .then(() => mutate()) + .then(() => onSearchLocal?.(searchText)) + .catch((e) => { + console.debug(e) }) - console.error(err) - } + .finally(() => setSearching(false)) }, 300) const onSearchChanged = useCallback( @@ -80,13 +69,24 @@ const ModelSearch = ({ onSearchLocal }: Props) => { return ( } - placeholder="Search or paste Hugging Face URL" + ref={inputRef} + prefixIcon={ + isSearching ? ( + + ) : ( + + ) + } + placeholder="Search or enter Hugging Face URL" onChange={onSearchChanged} onKeyDown={onKeyDown} value={searchText} clearable={searchText.length > 0} onClear={onClear} + className="border-0 bg-[hsla(var(--app-bg))]" + onClick={() => { + onSearchLocal?.(inputRef.current?.value ?? '') + }} /> ) } diff --git a/web/containers/Providers/DeepLinkListener.tsx b/web/containers/Providers/DeepLinkListener.tsx index b991996a26..f8eebe6189 100644 --- a/web/containers/Providers/DeepLinkListener.tsx +++ b/web/containers/Providers/DeepLinkListener.tsx @@ -4,25 +4,21 @@ import { useSetAtom } from 'jotai' import { useDebouncedCallback } from 'use-debounce' -import { useGetHFRepoData } from '@/hooks/useGetHFRepoData' +import { MainViewState } from '@/constants/screens' + +import { useModelSourcesMutation } from '@/hooks/useModelSource' import { loadingModalInfoAtom } from '../LoadingModal' import { toaster } from '../Toast' -import { - importHuggingFaceModelStageAtom, - importingHuggingFaceRepoDataAtom, -} from '@/helpers/atoms/HuggingFace.atom' +import { mainViewStateAtom } from '@/helpers/atoms/App.atom' +import { modelDetailAtom } from '@/helpers/atoms/Model.atom' const DeepLinkListener: React.FC = () => { - const { getHfRepoData } = useGetHFRepoData() + const { addModelSource } = useModelSourcesMutation() const setLoadingInfo = useSetAtom(loadingModalInfoAtom) - const setImportingHuggingFaceRepoData = useSetAtom( - importingHuggingFaceRepoDataAtom - ) - const setImportHuggingFaceModelStage = useSetAtom( - importHuggingFaceModelStageAtom - ) + const setMainView = useSetAtom(mainViewStateAtom) + const setModelDetail = useSetAtom(modelDetailAtom) const handleDeepLinkAction = useDebouncedCallback( async (deepLinkAction: DeepLinkAction) => { @@ -38,17 +34,17 @@ const DeepLinkListener: React.FC = () => { try { setLoadingInfo({ - title: 'Getting Hugging Face models', + title: 'Getting Hugging Face model details', message: 'Please wait..', }) - const data = await getHfRepoData(deepLinkAction.resource) - setImportingHuggingFaceRepoData(data) - setImportHuggingFaceModelStage('REPO_DETAIL') + await addModelSource(deepLinkAction.resource) setLoadingInfo(undefined) + setMainView(MainViewState.Hub) + setModelDetail(deepLinkAction.resource) } catch (err) { setLoadingInfo(undefined) toaster({ - title: 'Failed to get Hugging Face models', + title: 'Failed to get Hugging Face model details', description: err instanceof Error ? err.message : 'Unexpected Error', type: 'error', }) diff --git a/web/helpers/atoms/App.atom.ts b/web/helpers/atoms/App.atom.ts index 7321fd9b41..07a9fb81ee 100644 --- a/web/helpers/atoms/App.atom.ts +++ b/web/helpers/atoms/App.atom.ts @@ -30,3 +30,15 @@ export const copyOverInstructionEnabledAtom = atomWithStorage( COPY_OVER_INSTRUCTION_ENABLED, false ) + +/** + * App Hub Banner configured image + */ +export const appBannerHubAtom = atomWithStorage( + 'appBannerHub', + './images/HubBanner/banner-8.jpg', + undefined, + { + getOnInit: true, + } +) diff --git a/web/helpers/atoms/HuggingFace.atom.test.ts b/web/helpers/atoms/HuggingFace.atom.test.ts deleted file mode 100644 index 134d199473..0000000000 --- a/web/helpers/atoms/HuggingFace.atom.test.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import { importHuggingFaceModelStageAtom } from './HuggingFace.atom'; -import { importingHuggingFaceRepoDataAtom } from './HuggingFace.atom'; - -test('importHuggingFaceModelStageAtom should have initial value of NONE', () => { - const result = importHuggingFaceModelStageAtom.init; - expect(result).toBe('NONE'); -}); - - -test('importingHuggingFaceRepoDataAtom should have initial value of undefined', () => { - const result = importingHuggingFaceRepoDataAtom.init; - expect(result).toBeUndefined(); -}); diff --git a/web/helpers/atoms/HuggingFace.atom.ts b/web/helpers/atoms/HuggingFace.atom.ts deleted file mode 100644 index 09f7870a38..0000000000 --- a/web/helpers/atoms/HuggingFace.atom.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { HuggingFaceRepoData } from '@janhq/core' -import { atom } from 'jotai' - -// modals -export type ImportHuggingFaceModelStage = 'NONE' | 'REPO_DETAIL' - -export const importingHuggingFaceRepoDataAtom = atom< - HuggingFaceRepoData | undefined ->(undefined) - -export const importHuggingFaceModelStageAtom = - atom('NONE') diff --git a/web/helpers/atoms/Model.atom.ts b/web/helpers/atoms/Model.atom.ts index 445e36a4a8..22f277294c 100644 --- a/web/helpers/atoms/Model.atom.ts +++ b/web/helpers/atoms/Model.atom.ts @@ -60,6 +60,11 @@ export const showEngineListModelAtom = atom([ InferenceEngine.cortex_tensorrtllm, ]) +/** + * Atom to store the current model detail page of a certain model id + */ +export const modelDetailAtom = atom(undefined) + /// End Models Atom /// Model Download Atom diff --git a/web/hooks/useEngineManagement.ts b/web/hooks/useEngineManagement.ts index fec5eb0ed6..b08004c4ef 100644 --- a/web/hooks/useEngineManagement.ts +++ b/web/hooks/useEngineManagement.ts @@ -10,12 +10,17 @@ import { EngineEvent, Model, ModelEvent, + ModelSource, + ModelSibling, } from '@janhq/core' -import { useAtom } from 'jotai' +import { useAtom, useAtomValue } from 'jotai' import { atomWithStorage } from 'jotai/utils' import useSWR from 'swr' +import { getDescriptionByEngine, getTitleByEngine } from '@/utils/modelEngine' + import { extensionManager } from '@/extension/ExtensionManager' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' export const releasedEnginesCacheAtom = atomWithStorage<{ data: EngineReleased[] @@ -415,3 +420,39 @@ export const addRemoteEngineModel = async (name: string, engine: string) => { throw error } } + +/** + * Remote model sources + * @returns A Promise that resolves to an object of model sources. + */ +export const useGetEngineModelSources = () => { + const { engines } = useGetEngines() + const downloadedModels = useAtomValue(downloadedModelsAtom) + + return { + sources: Object.entries(engines ?? {}) + ?.filter((e) => e?.[1]?.[0]?.type === 'remote') + .map( + ([key, values]) => + ({ + id: key, + models: ( + downloadedModels.filter((e) => e.engine === values[0]?.engine) ?? + [] + ).map( + (e) => + ({ + id: e.id, + size: e.metadata?.size, + }) as unknown as ModelSibling + ), + metadata: { + id: getTitleByEngine(key as InferenceEngine), + description: getDescriptionByEngine(key as InferenceEngine), + apiKey: values[0]?.api_key, + }, + type: 'cloud', + }) as unknown as ModelSource + ), + } +} diff --git a/web/hooks/useModelSource.ts b/web/hooks/useModelSource.ts new file mode 100644 index 0000000000..a797586f3f --- /dev/null +++ b/web/hooks/useModelSource.ts @@ -0,0 +1,72 @@ +import { useMemo } from 'react' + +import { ExtensionTypeEnum, ModelExtension } from '@janhq/core' +import useSWR from 'swr' + +import { extensionManager } from '@/extension/ExtensionManager' + +/** + * @returns A Promise that resolves to an object of model sources. + */ +export function useGetModelSources() { + const extension = useMemo( + () => extensionManager.get(ExtensionTypeEnum.Model), + [] + ) + + const { + data: sources, + error, + mutate, + } = useSWR( + extension ? 'getSources' : null, + () => + extension?.getSources().then((e) => + e.map((m) => ({ + ...m, + models: m.models.sort((a, b) => a.size - b.size), + })) + ), + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { sources, error, mutate } +} + +export const useModelSourcesMutation = () => { + const extension = useMemo( + () => extensionManager.get(ExtensionTypeEnum.Model), + [] + ) + /** + * Add a new model source + * @returns A Promise that resolves to intall of engine. + */ + const addModelSource = async (source: string) => { + try { + // Call the extension's method + return await extension?.addSource(source) + } catch (error) { + console.error('Failed to install engine variant:', error) + throw error + } + } + + /** + * Delete a new model source + * @returns A Promise that resolves to intall of engine. + */ + const deleteModelSource = async (source: string) => { + try { + // Call the extension's method + return await extension?.deleteSource(source) + } catch (error) { + console.error('Failed to install engine variant:', error) + throw error + } + } + return { addModelSource, deleteModelSource } +} diff --git a/web/hooks/useModels.ts b/web/hooks/useModels.ts index 40586462ba..ec03e21583 100644 --- a/web/hooks/useModels.ts +++ b/web/hooks/useModels.ts @@ -32,12 +32,15 @@ const useModels = () => { const getData = useCallback(() => { const getDownloadedModels = async () => { - const localModels = (await getModels()).map((e) => ({ - ...e, - name: ModelManager.instance().models.get(e.id)?.name ?? e.name ?? e.id, - metadata: - ModelManager.instance().models.get(e.id)?.metadata ?? e.metadata, - })) + const localModels = (await getModels()) + .map((e) => ({ + ...e, + name: + ModelManager.instance().models.get(e.id)?.name ?? e.name ?? e.id, + metadata: + ModelManager.instance().models.get(e.id)?.metadata ?? e.metadata, + })) + .filter((e) => !('status' in e) || e.status !== 'downloadable') const remoteModels = ModelManager.instance() .models.values() diff --git a/web/jest.config.js b/web/jest.config.js index 96bfb2356d..dea8827cd0 100644 --- a/web/jest.config.js +++ b/web/jest.config.js @@ -40,5 +40,5 @@ const config = { // module.exports = createJestConfig(config) module.exports = async () => ({ ...(await createJestConfig(config)()), - transformIgnorePatterns: ['/node_modules/(?!(layerr|nanoid|@uppy|preact)/)'], + transformIgnorePatterns: ['/node_modules/(?!((.*))/)'], }) diff --git a/web/package.json b/web/package.json index c562b6aa66..13d433b3ad 100644 --- a/web/package.json +++ b/web/package.json @@ -48,6 +48,7 @@ "rehype-highlight": "^7.0.1", "rehype-highlight-code-lines": "^1.0.4", "rehype-katex": "^7.0.1", + "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "sass": "^1.69.4", "slate": "latest", diff --git a/web/public/icons/huggingFace.svg b/web/public/icons/huggingFace.svg new file mode 100644 index 0000000000..8570b2049c --- /dev/null +++ b/web/public/icons/huggingFace.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/public/images/HubBanner/banner-1.jpg b/web/public/images/HubBanner/banner-1.jpg new file mode 100644 index 0000000000..230920d16d Binary files /dev/null and b/web/public/images/HubBanner/banner-1.jpg differ diff --git a/web/public/images/HubBanner/banner-10.jpg b/web/public/images/HubBanner/banner-10.jpg new file mode 100644 index 0000000000..8ce5b5ec5c Binary files /dev/null and b/web/public/images/HubBanner/banner-10.jpg differ diff --git a/web/public/images/HubBanner/banner-11.jpg b/web/public/images/HubBanner/banner-11.jpg new file mode 100644 index 0000000000..66748dba6a Binary files /dev/null and b/web/public/images/HubBanner/banner-11.jpg differ diff --git a/web/public/images/HubBanner/banner-12.jpg b/web/public/images/HubBanner/banner-12.jpg new file mode 100644 index 0000000000..d59699a898 Binary files /dev/null and b/web/public/images/HubBanner/banner-12.jpg differ diff --git a/web/public/images/HubBanner/banner-13.jpg b/web/public/images/HubBanner/banner-13.jpg new file mode 100644 index 0000000000..6e8ccf6908 Binary files /dev/null and b/web/public/images/HubBanner/banner-13.jpg differ diff --git a/web/public/images/HubBanner/banner-14.jpg b/web/public/images/HubBanner/banner-14.jpg new file mode 100644 index 0000000000..b6afaa6c2d Binary files /dev/null and b/web/public/images/HubBanner/banner-14.jpg differ diff --git a/web/public/images/HubBanner/banner-15.jpg b/web/public/images/HubBanner/banner-15.jpg new file mode 100644 index 0000000000..30237060fa Binary files /dev/null and b/web/public/images/HubBanner/banner-15.jpg differ diff --git a/web/public/images/HubBanner/banner-16.jpg b/web/public/images/HubBanner/banner-16.jpg new file mode 100644 index 0000000000..dbd11da2cc Binary files /dev/null and b/web/public/images/HubBanner/banner-16.jpg differ diff --git a/web/public/images/HubBanner/banner-17.jpg b/web/public/images/HubBanner/banner-17.jpg new file mode 100644 index 0000000000..828c0df676 Binary files /dev/null and b/web/public/images/HubBanner/banner-17.jpg differ diff --git a/web/public/images/HubBanner/banner-18.jpg b/web/public/images/HubBanner/banner-18.jpg new file mode 100644 index 0000000000..4f8f4a9fb3 Binary files /dev/null and b/web/public/images/HubBanner/banner-18.jpg differ diff --git a/web/public/images/HubBanner/banner-19.jpg b/web/public/images/HubBanner/banner-19.jpg new file mode 100644 index 0000000000..9da970f7ae Binary files /dev/null and b/web/public/images/HubBanner/banner-19.jpg differ diff --git a/web/public/images/HubBanner/banner-2.jpg b/web/public/images/HubBanner/banner-2.jpg new file mode 100644 index 0000000000..7241f7ee02 Binary files /dev/null and b/web/public/images/HubBanner/banner-2.jpg differ diff --git a/web/public/images/HubBanner/banner-20.jpg b/web/public/images/HubBanner/banner-20.jpg new file mode 100644 index 0000000000..58348eea79 Binary files /dev/null and b/web/public/images/HubBanner/banner-20.jpg differ diff --git a/web/public/images/HubBanner/banner-21.jpg b/web/public/images/HubBanner/banner-21.jpg new file mode 100644 index 0000000000..3f4f3cd590 Binary files /dev/null and b/web/public/images/HubBanner/banner-21.jpg differ diff --git a/web/public/images/HubBanner/banner-22.jpg b/web/public/images/HubBanner/banner-22.jpg new file mode 100644 index 0000000000..54aa38834f Binary files /dev/null and b/web/public/images/HubBanner/banner-22.jpg differ diff --git a/web/public/images/HubBanner/banner-23.jpg b/web/public/images/HubBanner/banner-23.jpg new file mode 100644 index 0000000000..fbac143529 Binary files /dev/null and b/web/public/images/HubBanner/banner-23.jpg differ diff --git a/web/public/images/HubBanner/banner-24.jpg b/web/public/images/HubBanner/banner-24.jpg new file mode 100644 index 0000000000..0d1ac23c90 Binary files /dev/null and b/web/public/images/HubBanner/banner-24.jpg differ diff --git a/web/public/images/HubBanner/banner-25.jpg b/web/public/images/HubBanner/banner-25.jpg new file mode 100644 index 0000000000..a3a7e7ead5 Binary files /dev/null and b/web/public/images/HubBanner/banner-25.jpg differ diff --git a/web/public/images/HubBanner/banner-26.jpg b/web/public/images/HubBanner/banner-26.jpg new file mode 100644 index 0000000000..41ecc74a49 Binary files /dev/null and b/web/public/images/HubBanner/banner-26.jpg differ diff --git a/web/public/images/HubBanner/banner-27.jpg b/web/public/images/HubBanner/banner-27.jpg new file mode 100644 index 0000000000..d1d05325a4 Binary files /dev/null and b/web/public/images/HubBanner/banner-27.jpg differ diff --git a/web/public/images/HubBanner/banner-28.jpg b/web/public/images/HubBanner/banner-28.jpg new file mode 100644 index 0000000000..fa48c25590 Binary files /dev/null and b/web/public/images/HubBanner/banner-28.jpg differ diff --git a/web/public/images/HubBanner/banner-29.jpg b/web/public/images/HubBanner/banner-29.jpg new file mode 100644 index 0000000000..2e3ccaa85d Binary files /dev/null and b/web/public/images/HubBanner/banner-29.jpg differ diff --git a/web/public/images/HubBanner/banner-3.jpg b/web/public/images/HubBanner/banner-3.jpg new file mode 100644 index 0000000000..91dc1e4991 Binary files /dev/null and b/web/public/images/HubBanner/banner-3.jpg differ diff --git a/web/public/images/HubBanner/banner-30.jpg b/web/public/images/HubBanner/banner-30.jpg new file mode 100644 index 0000000000..464f8225c2 Binary files /dev/null and b/web/public/images/HubBanner/banner-30.jpg differ diff --git a/web/public/images/HubBanner/banner-4.jpg b/web/public/images/HubBanner/banner-4.jpg new file mode 100644 index 0000000000..582daf089d Binary files /dev/null and b/web/public/images/HubBanner/banner-4.jpg differ diff --git a/web/public/images/HubBanner/banner-5.jpg b/web/public/images/HubBanner/banner-5.jpg new file mode 100644 index 0000000000..8e732a2f04 Binary files /dev/null and b/web/public/images/HubBanner/banner-5.jpg differ diff --git a/web/public/images/HubBanner/banner-6.jpg b/web/public/images/HubBanner/banner-6.jpg new file mode 100644 index 0000000000..c2c807cba4 Binary files /dev/null and b/web/public/images/HubBanner/banner-6.jpg differ diff --git a/web/public/images/HubBanner/banner-7.jpg b/web/public/images/HubBanner/banner-7.jpg new file mode 100644 index 0000000000..9453023ed9 Binary files /dev/null and b/web/public/images/HubBanner/banner-7.jpg differ diff --git a/web/public/images/HubBanner/banner-8.jpg b/web/public/images/HubBanner/banner-8.jpg new file mode 100644 index 0000000000..e056b50d59 Binary files /dev/null and b/web/public/images/HubBanner/banner-8.jpg differ diff --git a/web/public/images/HubBanner/banner-9.jpg b/web/public/images/HubBanner/banner-9.jpg new file mode 100644 index 0000000000..6ff9b91905 Binary files /dev/null and b/web/public/images/HubBanner/banner-9.jpg differ diff --git a/web/public/images/hub-banner.png b/web/public/images/hub-banner.png deleted file mode 100644 index 2467b0c019..0000000000 Binary files a/web/public/images/hub-banner.png and /dev/null differ diff --git a/web/screens/Hub/ModelList/ModelHeader/index.tsx b/web/screens/Hub/ModelList/ModelHeader/index.tsx index 9a939aa445..9198469473 100644 --- a/web/screens/Hub/ModelList/ModelHeader/index.tsx +++ b/web/screens/Hub/ModelList/ModelHeader/index.tsx @@ -1,20 +1,14 @@ import { useCallback } from 'react' -import { Model } from '@janhq/core' -import { Button, Badge, Tooltip } from '@janhq/joi' +import { ModelSource } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { Button, Tooltip, Dropdown, Badge } from '@janhq/joi' +import { useAtomValue, useSetAtom } from 'jotai' import { ChevronDownIcon } from 'lucide-react' -import { twMerge } from 'tailwind-merge' - import ModalCancelDownload from '@/containers/ModalCancelDownload' -import ModelLabel from '@/containers/ModelLabel' - -import { toaster } from '@/containers/Toast' - import { MainViewState } from '@/constants/screens' import { useCreateNewThread } from '@/hooks/useCreateNewThread' @@ -22,7 +16,9 @@ import useDownloadModel from '@/hooks/useDownloadModel' import { useSettings } from '@/hooks/useSettings' -import { toGibibytes } from '@/utils/converter' +import { toGigabytes } from '@/utils/converter' + +import { extractModelName } from '@/utils/modelSource' import { mainViewStateAtom } from '@/helpers/atoms/App.atom' import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' @@ -32,25 +28,25 @@ import { downloadedModelsAtom, getDownloadingModelAtom, } from '@/helpers/atoms/Model.atom' +import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' import { nvidiaTotalVramAtom, totalRamAtom, } from '@/helpers/atoms/SystemBar.atom' type Props = { - model: Model - onClick: () => void - open: string + model: ModelSource + onSelectedModel: () => void } -const ModelItemHeader = ({ model, onClick, open }: Props) => { +const ModelItemHeader = ({ model, onSelectedModel }: Props) => { const { downloadModel } = useDownloadModel() const downloadingModels = useAtomValue(getDownloadingModelAtom) const downloadedModels = useAtomValue(downloadedModelsAtom) + const setSelectedSetting = useSetAtom(selectedSettingAtom) const { requestCreateNewThread } = useCreateNewThread() const totalRam = useAtomValue(totalRamAtom) const { settings } = useSettings() - // const [imageLoaded, setImageLoaded] = useState(true) const nvidiaTotalVram = useAtomValue(nvidiaTotalVramAtom) const setMainViewState = useSetAtom(mainViewStateAtom) @@ -64,36 +60,68 @@ const ModelItemHeader = ({ model, onClick, open }: Props) => { const assistants = useAtomValue(assistantsAtom) const onDownloadClick = useCallback(() => { - downloadModel(model.sources[0].url, model.id, model.name) + downloadModel(model.models?.[0].id) }, [model, downloadModel]) - const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null + const isDownloaded = downloadedModels.some((md) => + model.models.some((m) => m.id === md.id) + ) let downloadButton = ( - +
+
+ Download +
+ ({ + name: ( +
+ + {e.id} + + + Default + +
+ ), + value: e.id, + suffix: toGigabytes(e.size), + }))} + onValueChanged={(e) => downloadModel(e)} + > +
+ +
+
+
) - const isDownloading = downloadingModels.some((md) => md === model.id) + const isDownloading = downloadingModels.some((md) => + model.models.some((m) => m.id === md) + ) const onUseModelClick = useCallback(async () => { - if (assistants.length === 0) { - toaster({ - title: 'No assistant available.', - description: `Could not use Model ${model.name} as no assistant is available.`, - type: 'error', - }) - return + const downloadedModel = downloadedModels.find((e) => + model.models.some((m) => m.id === e.id) + ) + if (downloadedModel) { + await requestCreateNewThread(assistants[0], downloadedModel) + setMainViewState(MainViewState.Thread) } - await requestCreateNewThread(assistants[0], model) - setMainViewState(MainViewState.Thread) - }, [assistants, model, requestCreateNewThread, setMainViewState]) + }, [ + assistants, + model, + requestCreateNewThread, + setMainViewState, + downloadedModels, + ]) if (isDownloaded) { downloadButton = ( @@ -104,6 +132,7 @@ const ModelItemHeader = ({ model, onClick, open }: Props) => { disabled={serverEnabled} data-testid={`use-model-btn-${model.id}`} variant="outline" + theme="ghost" className="min-w-[98px]" > Use @@ -114,54 +143,54 @@ const ModelItemHeader = ({ model, onClick, open }: Props) => { /> ) } else if (isDownloading) { - downloadButton = + downloadButton = ( + model.models.some((m) => m.id === e)) ?? + model.id + } + /> + ) } return ( -
-
-
- - {model.name} +
+
+
+ + {extractModelName(model.metadata?.id)} -
- - {toGibibytes(model.metadata?.size)} + + {toGigabytes(model.models?.[0]?.size)} -
- {downloadButton} - + {model.type !== 'cloud' ? ( + downloadButton + ) : ( + <> + {!model.metadata?.apiKey?.length && ( + + )} + + )}
) } -type EngineBadgeProps = { - engine: string -} - -const EngineBadge = ({ engine }: EngineBadgeProps) => { - const title = 'TensorRT-LLM' - - switch (engine) { - case 'nitro-tensorrt-llm': - return {title} - default: - return null - } -} - export default ModelItemHeader diff --git a/web/screens/Hub/ModelList/ModelItem/index.tsx b/web/screens/Hub/ModelList/ModelItem/index.tsx index a077dbffc2..b2f0b7e8ad 100644 --- a/web/screens/Hub/ModelList/ModelItem/index.tsx +++ b/web/screens/Hub/ModelList/ModelItem/index.tsx @@ -1,98 +1,76 @@ -import { useState } from 'react' +import Markdown from 'react-markdown' -import { Model } from '@janhq/core' -import { Badge } from '@janhq/joi' +import Image from 'next/image' -import { twMerge } from 'tailwind-merge' +import { ModelSource } from '@janhq/core' + +import { DownloadIcon, FileJson } from 'lucide-react' import ModelLabel from '@/containers/ModelLabel' import ModelItemHeader from '@/screens/Hub/ModelList/ModelHeader' -import { toGibibytes } from '@/utils/converter' +import { toGigabytes } from '@/utils/converter' +import { extractDescription } from '@/utils/modelSource' +import '@/styles/components/model.scss' type Props = { - model: Model + model: ModelSource + onSelectedModel: () => void } -const ModelItem: React.FC = ({ model }) => { - const [open, setOpen] = useState('') - - const handleToggle = () => { - if (open === model.id) { - setOpen('') - } else { - setOpen(model.id) - } - } - +const ModelItem: React.FC = ({ model, onSelectedModel }) => { return ( -
- - {open === model.id && ( -
-
-
- - {toGibibytes(model.metadata?.size)} - - -
-
- About -

- {model.description || '-'} -

-
-
-
- Author -

- {model.metadata?.author} -

-
-
- Model ID -

- {model.id} -

-
-
- Tags -
- {model.metadata?.tags?.map((tag: string) => ( - - {tag} - - ))} -
-
-
-
+
+ -
-
- Format +
+
+
+ {toGigabytes(model.models?.[0]?.size)} + +
+
+ + {extractDescription(model.metadata?.description) || '-'} + +
+
+ {model.metadata?.author && (

- {model.format} + {model.id?.includes('huggingface.co') && ( + <> + {' '} + + )}{' '} + {model.metadata?.author} +

+ )} + {model.models?.length > 0 && ( +

+ + {model.models?.length}{' '} + {model.type === 'cloud' ? 'models' : 'versions'} +

+ )} + {model.metadata?.downloads > 0 && ( +

+ + {model.metadata?.downloads}

-
+ )}
- )} +
) } diff --git a/web/screens/Hub/ModelList/index.tsx b/web/screens/Hub/ModelList/index.tsx index 4c6c7d993a..5db4318083 100644 --- a/web/screens/Hub/ModelList/index.tsx +++ b/web/screens/Hub/ModelList/index.tsx @@ -1,53 +1,22 @@ -import { useMemo } from 'react' - -import { Model } from '@janhq/core' - -import { useAtomValue } from 'jotai' - -import { useGetEngines } from '@/hooks/useEngineManagement' +import { ModelSource } from '@janhq/core' import ModelItem from '@/screens/Hub/ModelList/ModelItem' -import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' - type Props = { - models: Model[] + models: ModelSource[] + onSelectedModel: (model: ModelSource) => void } -const ModelList = ({ models }: Props) => { - const downloadedModels = useAtomValue(downloadedModelsAtom) - const { engines } = useGetEngines() - const sortedModels: Model[] = useMemo(() => { - const featuredModels: Model[] = [] - const remoteModels: Model[] = [] - const localModels: Model[] = [] - const remainingModels: Model[] = [] - models.forEach((m) => { - if (m.metadata?.tags?.includes('Featured')) { - featuredModels.push(m) - } else if (engines?.[m.engine]?.[0]?.type === 'remote') { - remoteModels.push(m) - } else if (downloadedModels.map((m) => m.id).includes(m.id)) { - localModels.push(m) - } else { - remainingModels.push(m) - } - }) - featuredModels.sort((m1, m2) => m1.metadata?.size - m2.metadata?.size) - localModels.sort((m1, m2) => m1.metadata?.size - m2.metadata?.size) - remainingModels.sort((m1, m2) => m1.metadata?.size - m2.metadata?.size) - remoteModels.sort((m1, m2) => m1.name.localeCompare(m2.name)) - return [ - ...featuredModels, - ...localModels, - ...remainingModels, - ...remoteModels, - ] - }, [models, downloadedModels, engines]) - +const ModelList = ({ models, onSelectedModel }: Props) => { return (
- {sortedModels?.map((model) => )} + {models.map((model) => ( + onSelectedModel(model)} + /> + ))}
) } diff --git a/web/screens/Hub/ModelPage/index.tsx b/web/screens/Hub/ModelPage/index.tsx new file mode 100644 index 0000000000..2311a5d4b1 --- /dev/null +++ b/web/screens/Hub/ModelPage/index.tsx @@ -0,0 +1,206 @@ +import Image from 'next/image' + +import { ModelSource } from '@janhq/core' +import { Badge, Button, ScrollArea } from '@janhq/joi' +import { useSetAtom } from 'jotai' +import { + ArrowLeftIcon, + DownloadIcon, + FileJson, + SettingsIcon, +} from 'lucide-react' +import '@/styles/components/marked.scss' + +import ModelDownloadButton from '@/containers/ModelDownloadButton' + +import { MainViewState } from '@/constants/screens' + +import { MarkdownTextMessage } from '@/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage' + +import { toGigabytes } from '@/utils/converter' +import { extractModelName } from '@/utils/modelSource' + +import { mainViewStateAtom } from '@/helpers/atoms/App.atom' +import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' + +type Props = { + model: ModelSource + onGoBack: () => void +} + +const ModelPage = ({ model, onGoBack }: Props) => { + const setSelectedSetting = useSetAtom(selectedSettingAtom) + const setMainViewState = useSetAtom(mainViewStateAtom) + return ( + +
+
+
+
+ +
+
+
+ {/* Header */} +
+ + {extractModelName(model.metadata.id)} + +
+ {model.type !== 'cloud' ? ( + + ) : ( + <> + {!model.metadata?.apiKey?.length ? ( + + ) : ( + + )} + + )} +
+
+
+ {model.metadata?.author && ( +

+ {model.id?.includes('huggingface.co') && ( + <> + {' '} + + )} + {model.metadata?.author} +

+ )} + {model.models?.length > 0 && ( +

+ + {model.models?.length}{' '} + {model.type === 'cloud' ? 'models' : 'versions'} +

+ )} + {model.metadata?.downloads > 0 && ( +

+ + {model.metadata?.downloads} +

+ )} +
+ {/* Table of versions */} +
+
+ + + + + {model.type !== 'cloud' && ( + <> + + + + )} + + + + + {model.models?.map((item, i) => { + return ( + + + {model.type !== 'cloud' && ( + <> + + + + )} + + + ) + })} + +
+ {model.type !== 'cloud' ? 'Version' : 'Models'} + + Format + + Size +
+ + {item.id?.split(':')?.pop()} + + {i === 0 && model.type !== 'cloud' && ( + + Default + + )} + + GGUF + + {toGigabytes(item.size)} + + {(model.type !== 'cloud' || + (model.metadata?.apiKey?.length ?? 0) > 0) && ( + + )} +
+
+
+ {/* README */} +
+ +
+
+
+
+
+ ) +} + +export default ModelPage diff --git a/web/screens/Hub/index.tsx b/web/screens/Hub/index.tsx index 382cf5667b..031e34d561 100644 --- a/web/screens/Hub/index.tsx +++ b/web/screens/Hub/index.tsx @@ -1,116 +1,453 @@ -import { useCallback, useState } from 'react' +/* eslint-disable @typescript-eslint/naming-convention */ +import { useCallback, useMemo, useRef, useState, useEffect } from 'react' + +import { useDropzone } from 'react-dropzone' import Image from 'next/image' -import { ScrollArea, Button, Select } from '@janhq/joi' +import { ModelSource } from '@janhq/core' + +import { ScrollArea, Button, Select, Tabs, useClickOutside } from '@janhq/joi' +import { motion as m } from 'framer-motion' + +import { useAtom, useSetAtom } from 'jotai' +import { ImagePlusIcon, UploadCloudIcon, UploadIcon } from 'lucide-react' -import { useAtomValue, useSetAtom } from 'jotai' -import { UploadIcon } from 'lucide-react' +import { twMerge } from 'tailwind-merge' -import BlankState from '@/containers/BlankState' import CenterPanelContainer from '@/containers/CenterPanelContainer' import ModelSearch from '@/containers/ModelSearch' +import { useGetEngineModelSources } from '@/hooks/useEngineManagement' import { setImportModelStageAtom } from '@/hooks/useImportModel' +import { + useGetModelSources, + useModelSourcesMutation, +} from '@/hooks/useModelSource' + import ModelList from '@/screens/Hub/ModelList' -import { - configuredModelsAtom, - downloadedModelsAtom, -} from '@/helpers/atoms/Model.atom' +import { extractModelRepo } from '@/utils/modelSource' +import { fuzzySearch } from '@/utils/search' + +import ModelPage from './ModelPage' + +import { appBannerHubAtom } from '@/helpers/atoms/App.atom' +import { modelDetailAtom } from '@/helpers/atoms/Model.atom' const sortMenus = [ { - name: 'All Models', - value: 'all-models', + name: 'Most downloaded', + value: 'most-downloaded', }, { - name: 'Featured', - value: 'featured', + name: 'Newest', + value: 'newest', }, +] +const filterOptions = [ { - name: 'Downloaded', - value: 'downloaded', + name: 'All', + value: 'all', + }, + { + name: 'On-device', + value: 'on-device', + }, + { + name: 'Cloud', + value: 'cloud', }, ] const HubScreen = () => { - const configuredModels = useAtomValue(configuredModelsAtom) - const downloadedModels = useAtomValue(downloadedModelsAtom) - const [searchValue, setsearchValue] = useState('') - const [sortSelected, setSortSelected] = useState('all-models') - + const { sources } = useGetModelSources() + const { sources: remoteModelSources } = useGetEngineModelSources() + const { addModelSource } = useModelSourcesMutation() + const [searchValue, setSearchValue] = useState('') + const [sortSelected, setSortSelected] = useState('newest') + const [filterOption, setFilterOption] = useState('all') + const [hubBannerOption, setHubBannerOption] = useState('gallery') + const [showHubBannerSetting, setShowHubBannerSetting] = useState(false) + const [appBannerHub, setAppBannerHub] = useAtom(appBannerHubAtom) + const [selectedModel, setSelectedModel] = useState( + undefined + ) + const [modelDetail, setModelDetail] = useAtom(modelDetailAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom) + const dropdownRef = useRef(null) + const imageInputRef = useRef(null) + const hubBannerSettingRef = useRef(null) - const filteredModels = configuredModels.filter((x) => { - if (sortSelected === 'downloaded') { - return ( - x.name.toLowerCase().includes(searchValue.toLowerCase()) && - downloadedModels.some((y) => y.id === x.id) - ) - } else if (sortSelected === 'featured') { - return ( - x.name.toLowerCase().includes(searchValue.toLowerCase()) && - x.metadata?.tags?.includes('Featured') - ) - } else { - return x.name.toLowerCase().includes(searchValue.toLowerCase()) + const searchedModels = useMemo( + () => + searchValue.length + ? (sources?.filter((e) => + fuzzySearch( + searchValue.replaceAll(' ', '').toLowerCase(), + e.id.toLowerCase() + ) + ) ?? []) + : [], + [sources, searchValue] + ) + + const sortedModels = useMemo(() => { + if (!sources) return [] + return sources.sort((a, b) => { + if (sortSelected === 'most-downloaded') { + return b.metadata.downloads - a.metadata.downloads + } else { + return ( + new Date(b.metadata.createdAt).getTime() - + new Date(a.metadata.createdAt).getTime() + ) + } + }) + }, [sortSelected, sources]) + + useEffect(() => { + if (modelDetail) { + setSelectedModel(sources?.find((e) => e.id === modelDetail)) + setModelDetail(undefined) } - }) + }, [modelDetail, sources, setModelDetail, addModelSource]) + + useEffect(() => { + if (selectedModel) { + // Try add the model source again to update it's data + addModelSource(selectedModel.id).catch(console.debug) + } + }, [sources, selectedModel, addModelSource, setSelectedModel]) + + useClickOutside( + () => { + setSearchValue('') + }, + null, + [dropdownRef.current] + ) + + useClickOutside( + () => { + setShowHubBannerSetting(false) + }, + null, + [hubBannerSettingRef.current] + ) const onImportModelClick = useCallback(() => { setImportModelStage('SELECTING_MODEL') }, [setImportModelStage]) const onSearchUpdate = useCallback((input: string) => { - setsearchValue(input) + setSearchValue(input) }, []) + const setBannerHubImage = (image: string) => { + setShowHubBannerSetting(false) + setAppBannerHub(image) + } + + /** + * Handles the change event of the extension file input element by setting the file name state. + * Its to be used to display the extension file name of the selected file. + * @param event - The change event object. + */ + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + + const fileType = file.type + if (!fileType.startsWith('image/')) { + alert('Please upload an image file.') + return + } + + const reader = new FileReader() + reader.onload = () => { + // FileReader result is already in a valid Base64 format + setBannerHubImage(reader.result as string) + } + reader.readAsDataURL(file) + } + + const { isDragActive, getRootProps } = useDropzone({ + noClick: true, + multiple: true, + accept: { + 'image/jpeg': ['.jpeg'], + 'image/png': ['.png'], + 'image/jpg': ['.jpg'], + }, + onDrop: (files) => { + const reader = new FileReader() + reader.onload = () => { + // FileReader result is already in a valid Base64 format + setBannerHubImage(reader.result as string) + } + reader.readAsDataURL(files[0]) + }, + }) + return ( - -
- Hub Banner -
-
-
- -
-
- -
-
-
-
-
- {!filteredModels.length ? ( - - ) : ( + + {!selectedModel && ( + <> -
- +
+
+
+ )} +
+
+
+
+
+
+
+ +
0 && 'visible' + )} + > + {searchedModels.map((model) => ( +
{ + setSelectedModel(model) + e.stopPropagation() + }} + > + + {searchValue.includes('huggingface.co') && ( + <> + {' '} + + )} + {extractModelRepo(model.id)} + +
+ ))} +
+
+
+
+
+ +
+
+
+ <> +
+
+
+ {filterOptions.map((e) => ( +
+ +
+ ))} +
+
+
+