Merge branch 'dev' into main

This commit is contained in:
Louis 2024-04-15 14:57:31 +07:00 committed by GitHub
commit 9369ac3e8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 130 additions and 45 deletions

View File

@ -54,6 +54,7 @@ jobs:
- name: "Cleanup cache" - name: "Cleanup cache"
continue-on-error: true continue-on-error: true
run: | run: |
rm -rf ~/jan
make clean make clean
- name: Get Commit Message for PR - name: Get Commit Message for PR
@ -98,6 +99,7 @@ jobs:
- name: "Cleanup cache" - name: "Cleanup cache"
continue-on-error: true continue-on-error: true
run: | run: |
rm -rf ~/jan
make clean make clean
- name: Linter and test - name: Linter and test
@ -109,7 +111,7 @@ jobs:
CSC_IDENTITY_AUTO_DISCOVERY: "false" CSC_IDENTITY_AUTO_DISCOVERY: "false"
test-on-windows: test-on-windows:
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: github.event_name == 'push'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -131,6 +133,12 @@ jobs:
shell: powershell shell: powershell
continue-on-error: true continue-on-error: true
run: | run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean make clean
- name: Get Commit Message for push event - name: Get Commit Message for push event
@ -173,6 +181,12 @@ jobs:
shell: powershell shell: powershell
continue-on-error: true continue-on-error: true
run: | run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean make clean
- name: Get Commit Message for PR - name: Get Commit Message for PR
@ -216,6 +230,12 @@ jobs:
shell: powershell shell: powershell
continue-on-error: true continue-on-error: true
run: | run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean make clean
- name: Linter and test - name: Linter and test
@ -243,6 +263,7 @@ jobs:
- name: "Cleanup cache" - name: "Cleanup cache"
continue-on-error: true continue-on-error: true
run: | run: |
rm -rf ~/jan
make clean make clean
- name: Get Commit Message for PR - name: Get Commit Message for PR
@ -289,6 +310,7 @@ jobs:
- name: "Cleanup cache" - name: "Cleanup cache"
continue-on-error: true continue-on-error: true
run: | run: |
rm -rf ~/jan
make clean make clean
- name: Linter and test - name: Linter and test

View File

@ -47,11 +47,11 @@ jobs:
with: with:
args: | args: |
Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}: Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}:
- Windows: https://app.jan.ai/download/nightly/win-x64 - Windows: https://delta.jan.ai/latest/jan-win-x64-{{ VERSION }}.exe
- macOS Intel: https://app.jan.ai/download/nightly/mac-x64 - macOS Intel: https://delta.jan.ai/latest/jan-mac-x64-{{ VERSION }}.dmg
- macOS Apple Silicon: https://app.jan.ai/download/nightly/mac-arm64 - macOS Apple Silicon: https://delta.jan.ai/latest/jan-mac-arm64-{{ VERSION }}.dmg
- Linux Deb: https://app.jan.ai/download/nightly/linux-amd64-deb - Linux Deb: https://delta.jan.ai/latest/jan-linux-amd64-{{ VERSION }}.deb
- Linux AppImage: https://app.jan.ai/download/nightly/linux-amd64-appimage - 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 }} - Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
env: env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -79,7 +79,7 @@ common:
securityContext: {} securityContext: {}
service: service:
extenalLabel: {} externalLabel: {}
type: ClusterIP type: ClusterIP
port: 1337 port: 1337
targetPort: 1337 targetPort: 1337
@ -193,7 +193,7 @@ common:
securityContext: {} securityContext: {}
service: service:
extenalLabel: {} externalLabel: {}
type: ClusterIP type: ClusterIP
port: 3000 port: 3000
targetPort: 3000 targetPort: 3000

View File

@ -60,7 +60,7 @@ const joinPath: (paths: string[]) => Promise<string> = (paths) =>
globalThis.core.api?.joinPath(paths) globalThis.core.api?.joinPath(paths)
/** /**
* Retrive the basename from an url. * Retrieve the basename from an url.
* @param path - The path to retrieve. * @param path - The path to retrieve.
* @returns {Promise<string>} A promise that resolves with the basename. * @returns {Promise<string>} A promise that resolves with the basename.
*/ */

View File

@ -4,7 +4,7 @@ import { log } from './logger'
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => { export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
const cpu = await physicalCpuCount() const cpu = await physicalCpuCount()
log(`[NITRO]::CPU informations - ${cpu}`) log(`[NITRO]::CPU information - ${cpu}`)
return { return {
numCpuPhysicalCore: cpu, numCpuPhysicalCore: cpu,

View File

@ -20,6 +20,7 @@ export type InputComponentProps = {
placeholder: string placeholder: string
value: string value: string
type?: InputType type?: InputType
textAlign?: 'left' | 'right'
} }
export type SliderComponentProps = { export type SliderComponentProps = {

View File

@ -1,4 +1,4 @@
import { test, appInfo } from '../config/fixtures' import { test, appInfo, page, TIMEOUT } from '../config/fixtures'
import { expect } from '@playwright/test' import { expect } from '@playwright/test'
test.beforeAll(async () => { test.beforeAll(async () => {
@ -16,4 +16,9 @@ test.beforeAll(async () => {
test('explores hub', async ({ hubPage }) => { test('explores hub', async ({ hubPage }) => {
await hubPage.navigateByMenu() await hubPage.navigateByMenu()
await hubPage.verifyContainerVisible() await hubPage.verifyContainerVisible()
const useModelBtn= page.getByTestId(/^use-model-btn-.*/).first()
await expect(useModelBtn).toBeVisible({
timeout: TIMEOUT,
})
}) })

View File

@ -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,
})
})

View File

@ -8,8 +8,9 @@ export class BasePage {
constructor( constructor(
protected readonly page: Page, protected readonly page: Page,
readonly action: CommonActions, readonly action: CommonActions,
protected containerId: string protected containerId: string,
) {} ) {
}
public getValue(key: string) { public getValue(key: string) {
return this.action.getValue(key) return this.action.getValue(key)
@ -24,7 +25,11 @@ export class BasePage {
} }
async navigateByMenu() { 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() { async verifyContainerVisible() {
@ -36,7 +41,7 @@ export class BasePage {
await this.isElementVisible('img[alt="Jan - Logo"]') 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) { async isElementVisible(selector: any) {
let isVisible = true let isVisible = true
await this.page await this.page

View File

@ -22,7 +22,7 @@ export const getSelectedText = async () => {
/** /**
* Registers a global shortcut of `accelerator`. The `callback` is called * 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 * Returns `true` if the shortcut was registered successfully
*/ */

View File

@ -3,7 +3,7 @@ import { app } from 'electron'
export const setupCore = async () => { export const setupCore = async () => {
// Setup core api for main process // Setup core api for main process
global.core = { 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'), appPath: () => app.getPath('userData'),
} }
} }

View File

@ -42,7 +42,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine {
/** /**
* The interval id for the health check. Used to stop the health check. * 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. * 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.inferenceUrl = `${window.core?.api?.baseApiUrl}/v1/chat/completions`
} }
this.getNitroProcesHealthIntervalId = setInterval( this.getNitroProcessHealthIntervalId = setInterval(
() => this.periodicallyGetNitroHealth(), () => this.periodicallyGetNitroHealth(),
JanInferenceNitroExtension._intervalHealthCheck JanInferenceNitroExtension._intervalHealthCheck
) )
@ -95,7 +95,7 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine {
override loadModel(model: Model): Promise<void> { override loadModel(model: Model): Promise<void> {
if (model.engine !== this.provider) return Promise.resolve() if (model.engine !== this.provider) return Promise.resolve()
this.getNitroProcesHealthIntervalId = setInterval( this.getNitroProcessHealthIntervalId = setInterval(
() => this.periodicallyGetNitroHealth(), () => this.periodicallyGetNitroHealth(),
JanInferenceNitroExtension._intervalHealthCheck JanInferenceNitroExtension._intervalHealthCheck
) )
@ -106,9 +106,9 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine {
if (model?.engine && model.engine !== this.provider) return if (model?.engine && model.engine !== this.provider) return
// stop the periocally health check // stop the periocally health check
if (this.getNitroProcesHealthIntervalId) { if (this.getNitroProcessHealthIntervalId) {
clearInterval(this.getNitroProcesHealthIntervalId) clearInterval(this.getNitroProcessHealthIntervalId)
this.getNitroProcesHealthIntervalId = undefined this.getNitroProcessHealthIntervalId = undefined
} }
return super.unloadModel(model) return super.unloadModel(model)
} }

View File

@ -15,7 +15,8 @@
"controllerType": "input", "controllerType": "input",
"controllerProps": { "controllerProps": {
"value": "120000", "value": "120000",
"placeholder": "Interval in milliseconds. E.g. 120000" "placeholder": "Interval in milliseconds. E.g. 120000",
"textAlign": "right"
} }
} }
] ]

View File

@ -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 * Will be called when the extension is loaded to turn on GPU acceleration if supported
*/ */
export const updateNvidiaInfo = async () => { export const updateNvidiaInfo = async () => {

View File

@ -53,7 +53,7 @@ export class FileLogger extends Logger {
daysToKeep?: number | undefined daysToKeep?: number | undefined
): void { ): void {
// clear existing timeout // clear existing timeout
// incase we rerun it with different values // in case we rerun it with different values
if (this.timeout) clearTimeout(this.timeout) if (this.timeout) clearTimeout(this.timeout)
this.timeout = undefined this.timeout = undefined

View File

@ -12,7 +12,7 @@ export async function setup() {
if (!existsSync(appDir)) mkdirSync(appDir) if (!existsSync(appDir)) mkdirSync(appDir)
//@ts-ignore //@ts-ignore
global.core = { global.core = {
// Define appPath function for app to retrieve app path globaly // Define appPath function for app to retrieve app path globally
appPath: () => appDir, appPath: () => appDir,
} }
init({ init({

View File

@ -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. * 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 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 ### Key Design Decisions

View File

@ -14,8 +14,8 @@ Modular Architecture w/ Plugins:
- Jan will have an architecture similar to VSCode or k8Lens - Jan will have an architecture similar to VSCode or k8Lens
- "Desktop Application" whose functionality can be extended thru plugins - "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) - 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 accomodate plugins for different "model backends"(a) llama.cpp(b) rkwk (and others)(c) 3rd-party AIs - Nitro's architecture will need to accommodate plugins for different "model backends"(a) llama.cpp(b) rkwk (and others)(c) 3rd-party AIs
## Decision ## Decision

View File

@ -14,7 +14,7 @@ Proposed
## Context ## Context
What is the issue that we're seeing that is motivating this decision or change? 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 - 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 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 - 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

View File

@ -2,14 +2,20 @@ import { forwardRef } from 'react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {} extends React.InputHTMLAttributes<HTMLInputElement> {
textAlign?: 'left' | 'right'
}
const Input = forwardRef<HTMLInputElement, InputProps>( const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, textAlign, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
className={twMerge('input', className)} className={twMerge(
'input',
className,
textAlign === 'right' && 'text-right'
)}
ref={ref} ref={ref}
{...props} {...props}
/> />

View File

@ -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 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 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; @apply file:border-0 file:bg-transparent file:font-medium;
&.text-right {
text-align: right;
}
} }

View File

@ -4,7 +4,7 @@
} }
&-content { &-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 { &-close {

View File

@ -62,7 +62,7 @@ export default function Error({
> >
contact us contact us
</a>{' '} </a>{' '}
if the problem presists. if the problem persists.
</p> </p>
<div <div
className="mt-5 w-full rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700" className="mt-5 w-full rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"

View File

@ -44,8 +44,8 @@ export enum UsecaseTag {
UsecaseDefault = 'UsecaseDefault', UsecaseDefault = 'UsecaseDefault',
} }
export enum MiscellanousTag { export enum MiscellaneousTag {
MiscellanousDefault = 'MiscellanousDefault', MiscellaneousDefault = 'MiscellaneousDefault',
} }
export type TagType = export type TagType =
@ -59,4 +59,4 @@ export type TagType =
| NumOfBit | NumOfBit
| RamRequired | RamRequired
| UsecaseTag | UsecaseTag
| MiscellanousTag | MiscellaneousTag

View File

@ -138,7 +138,7 @@ const DropdownListSidebar = ({
...model?.parameters, ...model?.parameters,
...model?.settings, ...model?.settings,
} }
// Update model paramter to the thread state // Update model parameter to the thread state
setThreadModelParams(activeThread.id, modelParams) setThreadModelParams(activeThread.id, modelParams)
// Update model parameter to the thread file // Update model parameter to the thread file

View File

@ -31,6 +31,7 @@ export default function GenerateResponse() {
<div <div
className="absolute left-0 top-0 h-full bg-gray-200" className="absolute left-0 top-0 h-full bg-gray-200"
style={{ width: `${loader}%` }} style={{ width: `${loader}%` }}
data-testid="response-loader"
/> />
<span className="relative z-10">Generating response...</span> <span className="relative z-10">Generating response...</span>
</div> </div>

View File

@ -40,6 +40,7 @@ export default function ModelStart() {
<div <div
className="absolute left-0 top-0 h-full bg-blue-200" className="absolute left-0 top-0 h-full bg-blue-200"
style={{ width: `${loader}%` }} style={{ width: `${loader}%` }}
data-testid="model-loader"
/> />
<span className="relative z-10"> <span className="relative z-10">
{stateModel.state === 'start' ? 'Starting' : 'Stopping'} {stateModel.state === 'start' ? 'Starting' : 'Stopping'}

View File

@ -81,6 +81,7 @@ const SliderRightPanel: React.FC<Props> = ({
max={max} max={max}
value={String(value)} value={String(value)}
disabled={disabled} disabled={disabled}
textAlign="right"
onBlur={(e) => { onBlur={(e) => {
if (Number(e.target.value) > Number(max)) { if (Number(e.target.value) > Number(max)) {
onValueChanged?.(Number(max)) onValueChanged?.(Number(max))

View File

@ -107,7 +107,7 @@ export default function useGetSystemResources() {
return { return {
/** /**
* Fetch resource informations once * Fetch resource information once
*/ */
getSystemResources, getSystemResources,
/** /**

View File

@ -148,6 +148,7 @@ const ChatInput: React.FC = () => {
'max-h-[400px] resize-none pr-20', 'max-h-[400px] resize-none pr-20',
fileUpload.length && 'rounded-t-none' fileUpload.length && 'rounded-t-none'
)} )}
data-testid="txt-input-chat"
style={{ height: '40px' }} style={{ height: '40px' }}
ref={textareaRef} ref={textareaRef}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
@ -320,6 +321,7 @@ const ChatInput: React.FC = () => {
} }
themes="primary" themes="primary"
className="min-w-[100px]" className="min-w-[100px]"
data-testid="btn-send-chat"
onClick={() => sendChatMessage(currentPrompt)} onClick={() => sendChatMessage(currentPrompt)}
> >
Send Send

View File

@ -43,7 +43,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
case ErrorCode.InvalidApiKey: case ErrorCode.InvalidApiKey:
case ErrorCode.InvalidRequestError: case ErrorCode.InvalidRequestError:
return ( return (
<span> <span data-testid="invalid-API-key-error">
Invalid API key. Please check your API key from{' '} Invalid API key. Please check your API key from{' '}
<button <button
className="font-medium text-primary dark:text-blue-400" className="font-medium text-primary dark:text-blue-400"

View File

@ -113,6 +113,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
className="min-w-[98px]" className="min-w-[98px]"
onClick={onUseModelClick} onClick={onUseModelClick}
disabled={serverEnabled} disabled={serverEnabled}
data-testid={`use-model-btn-${model.id}`}
> >
Use Use
</Button> </Button>

View File

@ -18,7 +18,7 @@ export const HuggingFaceConvertingErrorModal = () => {
</div> </div>
<div className="flex flex-col items-center justify-center gap-1"> <div className="flex flex-col items-center justify-center gap-1">
<p className="text-center"> <p className="text-center">
An error occured while {conversionStatus} model {repoData.id}. An error occurred while {conversionStatus} model {repoData.id}.
</p> </p>
<p>Please close this modal and try again.</p> <p>Please close this modal and try again.</p>
</div> </div>

View File

@ -21,7 +21,7 @@ export const HuggingFaceSearchModal = () => {
return ( return (
<> <>
<div className="flex flex-col items-center justify-center gap-1"> <div className="flex flex-col items-center justify-center gap-1">
<p className="text-2xl font-bold">Hugging Face Convertor</p> <p className="text-2xl font-bold">Hugging Face Converter</p>
<p className="text-gray-500">Type the repository id below</p> <p className="text-gray-500">Type the repository id below</p>
</div> </div>
<Input <Input

View File

@ -21,7 +21,7 @@ const SettingDetailTextInputItem: React.FC<Props> = ({
settingProps, settingProps,
onValueChanged, onValueChanged,
}) => { }) => {
const { value, type, placeholder } = const { value, type, placeholder, textAlign } =
settingProps.controllerProps as InputComponentProps settingProps.controllerProps as InputComponentProps
const description = marked.parse(settingProps.description ?? '', { const description = marked.parse(settingProps.description ?? '', {
@ -43,6 +43,7 @@ const SettingDetailTextInputItem: React.FC<Props> = ({
<Input <Input
placeholder={placeholder} placeholder={placeholder}
type={type} type={type}
textAlign={textAlign}
value={value} value={value}
className="ml-4 w-[360px]" className="ml-4 w-[360px]"
onChange={(e) => onValueChanged?.(e.target.value)} onChange={(e) => onValueChanged?.(e.target.value)}