diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 9dd8cca8d..a9f934cfd 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -54,6 +54,7 @@ jobs: - name: "Cleanup cache" continue-on-error: true run: | + rm -rf ~/jan make clean - name: Get Commit Message for PR @@ -98,6 +99,7 @@ jobs: - name: "Cleanup cache" continue-on-error: true run: | + rm -rf ~/jan make clean - name: Linter and test @@ -109,7 +111,7 @@ jobs: CSC_IDENTITY_AUTO_DISCOVERY: "false" test-on-windows: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'push' strategy: fail-fast: false matrix: @@ -131,6 +133,12 @@ jobs: shell: powershell continue-on-error: true run: | + $path = "$Env:APPDATA\jan" + if (Test-Path $path) { + Remove-Item "\\?\$path" -Recurse -Force + } else { + Write-Output "Folder does not exist." + } make clean - name: Get Commit Message for push event @@ -173,6 +181,12 @@ jobs: shell: powershell continue-on-error: true run: | + $path = "$Env:APPDATA\jan" + if (Test-Path $path) { + Remove-Item "\\?\$path" -Recurse -Force + } else { + Write-Output "Folder does not exist." + } make clean - name: Get Commit Message for PR @@ -216,6 +230,12 @@ jobs: shell: powershell continue-on-error: true run: | + $path = "$Env:APPDATA\jan" + if (Test-Path $path) { + Remove-Item "\\?\$path" -Recurse -Force + } else { + Write-Output "Folder does not exist." + } make clean - name: Linter and test @@ -243,6 +263,7 @@ jobs: - name: "Cleanup cache" continue-on-error: true run: | + rm -rf ~/jan make clean - name: Get Commit Message for PR @@ -289,6 +310,7 @@ jobs: - name: "Cleanup cache" continue-on-error: true run: | + rm -rf ~/jan make clean - name: Linter and test diff --git a/.github/workflows/template-noti-discord-and-update-url-readme.yml b/.github/workflows/template-noti-discord-and-update-url-readme.yml index faf22bac4..c419a3a9a 100644 --- a/.github/workflows/template-noti-discord-and-update-url-readme.yml +++ b/.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -47,11 +47,11 @@ jobs: with: args: | Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}: - - Windows: https://app.jan.ai/download/nightly/win-x64 - - macOS Intel: https://app.jan.ai/download/nightly/mac-x64 - - macOS Apple Silicon: https://app.jan.ai/download/nightly/mac-arm64 - - Linux Deb: https://app.jan.ai/download/nightly/linux-amd64-deb - - Linux AppImage: https://app.jan.ai/download/nightly/linux-amd64-appimage + - Windows: https://delta.jan.ai/latest/jan-win-x64-{{ VERSION }}.exe + - macOS Intel: https://delta.jan.ai/latest/jan-mac-x64-{{ VERSION }}.dmg + - macOS Apple Silicon: https://delta.jan.ai/latest/jan-mac-arm64-{{ VERSION }}.dmg + - Linux Deb: https://delta.jan.ai/latest/jan-linux-amd64-{{ VERSION }}.deb + - Linux AppImage: https://delta.jan.ai/latest/jan-linux-x86_64-{{ VERSION }}.AppImage - Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} \ No newline at end of file diff --git a/charts/server/values.yaml b/charts/server/values.yaml index 73d4e8916..b31f47656 100644 --- a/charts/server/values.yaml +++ b/charts/server/values.yaml @@ -79,7 +79,7 @@ common: securityContext: {} service: - extenalLabel: {} + externalLabel: {} type: ClusterIP port: 1337 targetPort: 1337 @@ -193,7 +193,7 @@ common: securityContext: {} service: - extenalLabel: {} + externalLabel: {} type: ClusterIP port: 3000 targetPort: 3000 diff --git a/core/src/browser/core.ts b/core/src/browser/core.ts index 6bbae7c85..088c32e4e 100644 --- a/core/src/browser/core.ts +++ b/core/src/browser/core.ts @@ -60,7 +60,7 @@ const joinPath: (paths: string[]) => Promise = (paths) => globalThis.core.api?.joinPath(paths) /** - * Retrive the basename from an url. + * Retrieve the basename from an url. * @param path - The path to retrieve. * @returns {Promise} A promise that resolves with the basename. */ diff --git a/core/src/node/helper/resource.ts b/core/src/node/helper/resource.ts index 27e86c650..6c4a71478 100644 --- a/core/src/node/helper/resource.ts +++ b/core/src/node/helper/resource.ts @@ -4,7 +4,7 @@ import { log } from './logger' export const getSystemResourceInfo = async (): Promise => { const cpu = await physicalCpuCount() - log(`[NITRO]::CPU informations - ${cpu}`) + log(`[NITRO]::CPU information - ${cpu}`) return { numCpuPhysicalCore: cpu, diff --git a/core/src/types/setting/settingComponent.ts b/core/src/types/setting/settingComponent.ts index 4d9526505..e2bf667bd 100644 --- a/core/src/types/setting/settingComponent.ts +++ b/core/src/types/setting/settingComponent.ts @@ -20,6 +20,7 @@ export type InputComponentProps = { placeholder: string value: string type?: InputType + textAlign?: 'left' | 'right' } export type SliderComponentProps = { diff --git a/electron/tests/e2e/hub.e2e.spec.ts b/electron/tests/e2e/hub.e2e.spec.ts index d968e7641..23d4d0b6d 100644 --- a/electron/tests/e2e/hub.e2e.spec.ts +++ b/electron/tests/e2e/hub.e2e.spec.ts @@ -1,4 +1,4 @@ -import { test, appInfo } from '../config/fixtures' +import { test, appInfo, page, TIMEOUT } from '../config/fixtures' import { expect } from '@playwright/test' test.beforeAll(async () => { @@ -16,4 +16,9 @@ test.beforeAll(async () => { test('explores hub', async ({ hubPage }) => { await hubPage.navigateByMenu() await hubPage.verifyContainerVisible() + const useModelBtn= page.getByTestId(/^use-model-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 new file mode 100644 index 000000000..c13e91119 --- /dev/null +++ b/electron/tests/e2e/thread.e2e.spec.ts @@ -0,0 +1,35 @@ +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() + + // Attempt to create thread and chat in Thread page + await page + .getByTestId('btn-create-thread') + .click() + + await page + .getByTestId('txt-input-chat') + .fill('dummy value') + + await page + .getByTestId('btn-send-chat') + .click() + + await page.waitForFunction(() => { + const loaders = document.querySelectorAll('[data-testid$="loader"]'); + return !loaders.length; + }, { timeout: TIMEOUT }); + + const APIKeyError = page.getByTestId('invalid-API-key-error') + await expect(APIKeyError).toBeVisible({ + timeout: TIMEOUT, + }) +}) diff --git a/electron/tests/pages/basePage.ts b/electron/tests/pages/basePage.ts index 4e16a3c23..1817bc731 100644 --- a/electron/tests/pages/basePage.ts +++ b/electron/tests/pages/basePage.ts @@ -8,8 +8,9 @@ export class BasePage { constructor( protected readonly page: Page, readonly action: CommonActions, - protected containerId: string - ) {} + protected containerId: string, + ) { + } public getValue(key: string) { return this.action.getValue(key) @@ -24,7 +25,11 @@ export class BasePage { } async navigateByMenu() { - await this.page.getByTestId(this.menuId).first().click() + await this.clickFirstElement(this.menuId) + } + + async clickFirstElement(testId: string) { + await this.page.getByTestId(testId).first().click() } async verifyContainerVisible() { @@ -36,7 +41,7 @@ export class BasePage { await this.isElementVisible('img[alt="Jan - Logo"]') } - //wait and find a specific element with it's selector and return Visible + //wait and find a specific element with its selector and return Visible async isElementVisible(selector: any) { let isVisible = true await this.page diff --git a/electron/utils/selectedText.ts b/electron/utils/selectedText.ts index a39e331a9..25d31fab5 100644 --- a/electron/utils/selectedText.ts +++ b/electron/utils/selectedText.ts @@ -22,7 +22,7 @@ export const getSelectedText = async () => { /** * Registers a global shortcut of `accelerator`. The `callback` is called - * with the selected text when the registered shorcut is pressed by the user + * with the selected text when the registered shortcut is pressed by the user * * Returns `true` if the shortcut was registered successfully */ diff --git a/electron/utils/setup.ts b/electron/utils/setup.ts index 01b0b31da..d60ab47bb 100644 --- a/electron/utils/setup.ts +++ b/electron/utils/setup.ts @@ -3,7 +3,7 @@ import { app } from 'electron' export const setupCore = async () => { // Setup core api for main process global.core = { - // Define appPath function for app to retrieve app path globaly + // Define appPath function for app to retrieve app path globally appPath: () => app.getPath('userData'), } } diff --git a/extensions/inference-nitro-extension/src/index.ts b/extensions/inference-nitro-extension/src/index.ts index 1a8de783a..e6bad64f4 100644 --- a/extensions/inference-nitro-extension/src/index.ts +++ b/extensions/inference-nitro-extension/src/index.ts @@ -42,7 +42,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { /** * The interval id for the health check. Used to stop the health check. */ - private getNitroProcesHealthIntervalId: NodeJS.Timeout | undefined = undefined + private getNitroProcessHealthIntervalId: NodeJS.Timeout | undefined = undefined /** * Tracking the current state of nitro process. @@ -65,7 +65,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { this.inferenceUrl = `${window.core?.api?.baseApiUrl}/v1/chat/completions` } - this.getNitroProcesHealthIntervalId = setInterval( + this.getNitroProcessHealthIntervalId = setInterval( () => this.periodicallyGetNitroHealth(), JanInferenceNitroExtension._intervalHealthCheck ) @@ -95,7 +95,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { override loadModel(model: Model): Promise { if (model.engine !== this.provider) return Promise.resolve() - this.getNitroProcesHealthIntervalId = setInterval( + this.getNitroProcessHealthIntervalId = setInterval( () => this.periodicallyGetNitroHealth(), JanInferenceNitroExtension._intervalHealthCheck ) @@ -106,9 +106,9 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { if (model?.engine && model.engine !== this.provider) return // stop the periocally health check - if (this.getNitroProcesHealthIntervalId) { - clearInterval(this.getNitroProcesHealthIntervalId) - this.getNitroProcesHealthIntervalId = undefined + if (this.getNitroProcessHealthIntervalId) { + clearInterval(this.getNitroProcessHealthIntervalId) + this.getNitroProcessHealthIntervalId = undefined } return super.unloadModel(model) } diff --git a/extensions/monitoring-extension/resources/settings.json b/extensions/monitoring-extension/resources/settings.json index fbdaf309a..4e1d8d9d8 100644 --- a/extensions/monitoring-extension/resources/settings.json +++ b/extensions/monitoring-extension/resources/settings.json @@ -15,7 +15,8 @@ "controllerType": "input", "controllerProps": { "value": "120000", - "placeholder": "Interval in milliseconds. E.g. 120000" + "placeholder": "Interval in milliseconds. E.g. 120000", + "textAlign": "right" } } ] diff --git a/extensions/monitoring-extension/src/node/index.ts b/extensions/monitoring-extension/src/node/index.ts index 3f1be5609..049620026 100644 --- a/extensions/monitoring-extension/src/node/index.ts +++ b/extensions/monitoring-extension/src/node/index.ts @@ -138,7 +138,7 @@ export const getCurrentLoad = () => }) /** - * This will retrive GPU informations and persist settings.json + * This will retrieve GPU information and persist settings.json * Will be called when the extension is loaded to turn on GPU acceleration if supported */ export const updateNvidiaInfo = async () => { diff --git a/extensions/monitoring-extension/src/node/logger.ts b/extensions/monitoring-extension/src/node/logger.ts index 3d53e5ed9..9bafa7451 100644 --- a/extensions/monitoring-extension/src/node/logger.ts +++ b/extensions/monitoring-extension/src/node/logger.ts @@ -53,7 +53,7 @@ export class FileLogger extends Logger { daysToKeep?: number | undefined ): void { // clear existing timeout - // incase we rerun it with different values + // in case we rerun it with different values if (this.timeout) clearTimeout(this.timeout) this.timeout = undefined diff --git a/server/helpers/setup.ts b/server/helpers/setup.ts index 7d8f8914a..41595d70c 100644 --- a/server/helpers/setup.ts +++ b/server/helpers/setup.ts @@ -12,7 +12,7 @@ export async function setup() { if (!existsSync(appDir)) mkdirSync(appDir) //@ts-ignore global.core = { - // Define appPath function for app to retrieve app path globaly + // Define appPath function for app to retrieve app path globally appPath: () => appDir, } init({ diff --git a/specs/adrs/adr-001-jan-deployable-cloud-native.md b/specs/adrs/adr-001-jan-deployable-cloud-native.md index acc34d7fc..d07bd26ff 100644 --- a/specs/adrs/adr-001-jan-deployable-cloud-native.md +++ b/specs/adrs/adr-001-jan-deployable-cloud-native.md @@ -22,7 +22,7 @@ * This ADR aims to outline design decisions for deploying Jan in cloud native environments such as: Runpod, AWS, Azure, GCP in a fast and simple way. * The current code-base should not change too much. -* The current plugins must be reusable across enviroments (Desktop, Cloud-native). +* The current plugins must be reusable across environments (Desktop, Cloud-native). ### Key Design Decisions diff --git a/specs/adrs/adr-003-jan-plugins.md b/specs/adrs/adr-003-jan-plugins.md index 827682975..8dd5b282a 100644 --- a/specs/adrs/adr-003-jan-plugins.md +++ b/specs/adrs/adr-003-jan-plugins.md @@ -14,8 +14,8 @@ Modular Architecture w/ Plugins: - Jan will have an architecture similar to VSCode or k8Lens - "Desktop Application" whose functionality can be extended thru plugins -- Jan's architecture will need to accomodate plugins for (a) Persistence(b) IAM(c) Teams and RBAC(d) Policy engines(e) "Apps" (i.e. higher-order business logic)(f) Themes (UI) -- Nitro's architecture will need to accomodate plugins for different "model backends"(a) llama.cpp(b) rkwk (and others)(c) 3rd-party AIs +- Jan's architecture will need to accommodate plugins for (a) Persistence(b) IAM(c) Teams and RBAC(d) Policy engines(e) "Apps" (i.e. higher-order business logic)(f) Themes (UI) +- Nitro's architecture will need to accommodate plugins for different "model backends"(a) llama.cpp(b) rkwk (and others)(c) 3rd-party AIs ## Decision diff --git a/specs/adrs/adr-008-Extensible-Jan-with-Docker.md b/specs/adrs/adr-008-Extensible-Jan-with-Docker.md index 7b13864a2..05e72956a 100644 --- a/specs/adrs/adr-008-Extensible-Jan-with-Docker.md +++ b/specs/adrs/adr-008-Extensible-Jan-with-Docker.md @@ -14,7 +14,7 @@ Proposed ## Context What is the issue that we're seeing that is motivating this decision or change? -- The A.I world is moving fast with multiple runtime/ prebaked environment. We or the builder cannot cover just everything but rather we should adopt it and facillitate it as much as possible within Jan. +- The A.I world is moving fast with multiple runtime/ prebaked environment. We or the builder cannot cover just everything but rather we should adopt it and facilitate it as much as possible within Jan. - For `Run your own A.I`: Builder can build app on Jan (NodeJS env) and connect to external endpoint which serves the real A.I - e.g 1: Nitro acting as proxy to `triton-inference-server` running within a Docker container controlled by Jan app - e.g 2: Original models can be in many formats (pytorch, paddlepaddle). In order to run it with the most optimized version locally, there must be a step to transpile the model ([Ollama import model](https://github.com/jmorganca/ollama/blob/main/docs/import.md), Tensorrt). Btw Jan can prebuilt it and let user pull but later diff --git a/uikit/src/input/index.tsx b/uikit/src/input/index.tsx index 9b7808055..f6db8f0e9 100644 --- a/uikit/src/input/index.tsx +++ b/uikit/src/input/index.tsx @@ -2,14 +2,20 @@ import { forwardRef } from 'react' import { twMerge } from 'tailwind-merge' export interface InputProps - extends React.InputHTMLAttributes {} + extends React.InputHTMLAttributes { + textAlign?: 'left' | 'right' +} const Input = forwardRef( - ({ className, type, ...props }, ref) => { + ({ className, type, textAlign, ...props }, ref) => { return ( diff --git a/uikit/src/input/styles.scss b/uikit/src/input/styles.scss index e649f494d..62907cb33 100644 --- a/uikit/src/input/styles.scss +++ b/uikit/src/input/styles.scss @@ -3,4 +3,7 @@ @apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100 disabled:dark:bg-zinc-800 disabled:dark:text-zinc-600; @apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1; @apply file:border-0 file:bg-transparent file:font-medium; + &.text-right { + text-align: right; + } } diff --git a/uikit/src/modal/styles.scss b/uikit/src/modal/styles.scss index ee2699902..8a4d6c347 100644 --- a/uikit/src/modal/styles.scss +++ b/uikit/src/modal/styles.scss @@ -4,7 +4,7 @@ } &-content { - @apply bg-background border-border fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-4 shadow-lg duration-200 sm:rounded-lg md:w-full; + @apply bg-background border-border fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100%-48px)] w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto border p-4 shadow-lg duration-200 sm:rounded-lg md:w-full; } &-close { diff --git a/web/app/error.tsx b/web/app/error.tsx index 25b24b9ef..ccf752620 100644 --- a/web/app/error.tsx +++ b/web/app/error.tsx @@ -62,7 +62,7 @@ export default function Error({ > contact us {' '} - if the problem presists. + if the problem persists.

Generating response...
diff --git a/web/containers/Loader/ModelStart.tsx b/web/containers/Loader/ModelStart.tsx index f7bc04481..126044e01 100644 --- a/web/containers/Loader/ModelStart.tsx +++ b/web/containers/Loader/ModelStart.tsx @@ -40,6 +40,7 @@ export default function ModelStart() {
{stateModel.state === 'start' ? 'Starting' : 'Stopping'} diff --git a/web/containers/SliderRightPanel/index.tsx b/web/containers/SliderRightPanel/index.tsx index 3f7bef2c2..abbc7dfa4 100644 --- a/web/containers/SliderRightPanel/index.tsx +++ b/web/containers/SliderRightPanel/index.tsx @@ -81,6 +81,7 @@ const SliderRightPanel: React.FC = ({ max={max} value={String(value)} disabled={disabled} + textAlign="right" onBlur={(e) => { if (Number(e.target.value) > Number(max)) { onValueChanged?.(Number(max)) diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index 877b652cb..a05a6a710 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -107,7 +107,7 @@ export default function useGetSystemResources() { return { /** - * Fetch resource informations once + * Fetch resource information once */ getSystemResources, /** diff --git a/web/screens/Chat/ChatInput/index.tsx b/web/screens/Chat/ChatInput/index.tsx index 9c54c8c89..0b13fcd8c 100644 --- a/web/screens/Chat/ChatInput/index.tsx +++ b/web/screens/Chat/ChatInput/index.tsx @@ -148,6 +148,7 @@ const ChatInput: React.FC = () => { 'max-h-[400px] resize-none pr-20', fileUpload.length && 'rounded-t-none' )} + data-testid="txt-input-chat" style={{ height: '40px' }} ref={textareaRef} onKeyDown={onKeyDown} @@ -320,6 +321,7 @@ const ChatInput: React.FC = () => { } themes="primary" className="min-w-[100px]" + data-testid="btn-send-chat" onClick={() => sendChatMessage(currentPrompt)} > Send diff --git a/web/screens/Chat/ErrorMessage/index.tsx b/web/screens/Chat/ErrorMessage/index.tsx index 60f4a9ada..69f822dad 100644 --- a/web/screens/Chat/ErrorMessage/index.tsx +++ b/web/screens/Chat/ErrorMessage/index.tsx @@ -43,7 +43,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => { case ErrorCode.InvalidApiKey: case ErrorCode.InvalidRequestError: return ( - + Invalid API key. Please check your API key from{' '} diff --git a/web/screens/ExploreModels/HuggingFaceConvertingErrorModal/index.tsx b/web/screens/ExploreModels/HuggingFaceConvertingErrorModal/index.tsx index 863249d41..230409f1c 100644 --- a/web/screens/ExploreModels/HuggingFaceConvertingErrorModal/index.tsx +++ b/web/screens/ExploreModels/HuggingFaceConvertingErrorModal/index.tsx @@ -18,7 +18,7 @@ export const HuggingFaceConvertingErrorModal = () => {

- An error occured while {conversionStatus} model {repoData.id}. + An error occurred while {conversionStatus} model {repoData.id}.

Please close this modal and try again.

diff --git a/web/screens/ExploreModels/HuggingFaceSearchModal/index.tsx b/web/screens/ExploreModels/HuggingFaceSearchModal/index.tsx index a81df29fa..3add92ed1 100644 --- a/web/screens/ExploreModels/HuggingFaceSearchModal/index.tsx +++ b/web/screens/ExploreModels/HuggingFaceSearchModal/index.tsx @@ -21,7 +21,7 @@ export const HuggingFaceSearchModal = () => { return ( <>
-

Hugging Face Convertor

+

Hugging Face Converter

Type the repository id below

= ({ settingProps, onValueChanged, }) => { - const { value, type, placeholder } = + const { value, type, placeholder, textAlign } = settingProps.controllerProps as InputComponentProps const description = marked.parse(settingProps.description ?? '', { @@ -43,6 +43,7 @@ const SettingDetailTextInputItem: React.FC = ({ onValueChanged?.(e.target.value)}