Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/en/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,9 @@ pages:
The base url used to connect to remote service. Often looks
like http://localhost:11434/v1 or https://example.com/v1.
placeholder: Base url...
download-model:
label: Download Model
description: Download Chromium Prompt API model
headers:
label: Custom HTTP Headers
description: Add custom HTTP headers
Expand Down Expand Up @@ -980,6 +983,9 @@ pages:
title: List Models
check-supports-chat-completion:
title: Chat Completion
prompt-api:
check-availability:
title: Availability
status:
validating: Validating...
valid: Passed
Expand Down Expand Up @@ -1450,6 +1456,9 @@ pages:
browser-web-speech-api:
description: Browser-native STT (requires Chrome/Edge/Safari)
title: Web Speech API
prompt-api:
description: With the Prompt API, you can send natural language requests to the foundation model in Chrome.
title: Prompt API
official:
title: Official Provider
description: Official AI provider by AIRI.
Expand Down
475 changes: 340 additions & 135 deletions packages/i18n/src/locales/es/settings.yaml

Large diffs are not rendered by default.

410 changes: 304 additions & 106 deletions packages/i18n/src/locales/fr/settings.yaml

Large diffs are not rendered by default.

277 changes: 173 additions & 104 deletions packages/i18n/src/locales/ja/settings.yaml

Large diffs are not rendered by default.

287 changes: 186 additions & 101 deletions packages/i18n/src/locales/ko/settings.yaml

Large diffs are not rendered by default.

351 changes: 254 additions & 97 deletions packages/i18n/src/locales/ru/settings.yaml

Large diffs are not rendered by default.

322 changes: 229 additions & 93 deletions packages/i18n/src/locales/vi/settings.yaml

Large diffs are not rendered by default.

194 changes: 96 additions & 98 deletions packages/i18n/src/locales/zh-Hans/settings.yaml

Large diffs are not rendered by default.

248 changes: 156 additions & 92 deletions packages/i18n/src/locales/zh-Hant/settings.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/stage-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"vue-i18n": "catalog:",
"vue-router": "catalog:",
"vue-sonner": "catalog:",
"xsai-chromium-prompt": "catalog:",
"xsschema": "catalog:",
"zod": "catalog:"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script setup lang="ts">
import {
ProviderBasicSettings,
ProviderDownloadModel,
ProviderSettingsContainer,
ProviderSettingsLayout,
ProviderValidationAlerts,
} from '@proj-airi/stage-ui/components'
import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provider-validation'
import { checkPromptAvailability, downloadModel } from 'xsai-chromium-prompt'

const providerId = 'prompt-api'
// Define computed properties for credentials

// Use the composable to get validation logic and state
const {
t,
router,
providerMetadata,
isValidating,
isValid,
validationMessage,
handleResetSettings,
forceValid,
hasManualValidators,
isManualTesting,
manualTestPassed,
manualTestMessage,
runManualTest,
} = useProviderValidation(providerId)

async function checkAvailability() {
return await checkPromptAvailability()
}

async function download(onProgress: (progress: number) => void) {
return await downloadModel(onProgress)
}

function setVaild() {
isValid.value = true
}
</script>

<template>
<ProviderSettingsLayout
:provider-name="providerMetadata?.localizedName"
:provider-icon-color="providerMetadata?.iconColor"
:on-back="() => router.back()"
>
<ProviderSettingsContainer>
<ProviderBasicSettings
:title="t('settings.pages.providers.common.section.basic.title')"
:description="t('settings.pages.providers.common.section.basic.description')"
:on-reset="handleResetSettings"
>
<ProviderDownloadModel
:label="t('settings.pages.providers.catalog.edit.config.common.fields.field.download-model.label')"
:description="t('settings.pages.providers.catalog.edit.config.common.fields.field.download-model.label')"
Comment thread
AdairLi2504 marked this conversation as resolved.
Comment thread
AdairLi2504 marked this conversation as resolved.
:check-availability="checkAvailability"
:download="download"
:set-vaild="setVaild"
/>
</ProviderBasicSettings>
<ProviderValidationAlerts
:is-valid="isValid"
:is-validating="isValidating"
:validation-message="validationMessage"
:has-manual-validators="hasManualValidators"
:is-manual-testing="isManualTesting"
:manual-test-passed="manualTestPassed"
:manual-test-message="manualTestMessage"
:on-run-test="runManualTest"
:on-force-valid="forceValid"
:on-go-to-model-selection="() => router.push('/settings/modules/consciousness')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select Prompt API before opening model selection

When Prompt API is not already active, this callback only changes the route. The consciousness page loads models from the persisted activeProvider, so the action opens the previous provider or no model section instead of Prompt API. Set activeProvider to providerId before the route change, as the generic chat-provider page does.

Useful? React with 👍 / 👎.

/>
</ProviderSettingsContainer>
</ProviderSettingsLayout>
</template>

<route lang="yaml">
meta:
layout: settings
stageTransition:
name: slide
</route>
1 change: 1 addition & 0 deletions packages/stage-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"web-haptics": "catalog:",
"xast-util-to-xml": "catalog:",
"xastscript": "catalog:",
"xsai-chromium-prompt": "catalog:",
"xsschema": "catalog:",
"zod": "catalog:"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as ProviderAdvancedSettings } from './provider-advanced-setting
export { default as ProviderApiKeyInput } from './provider-api-key-input.vue'
export { default as ProviderBaseUrlInput } from './provider-base-url-input.vue'
export { default as ProviderBasicSettings } from './provider-basic-settings.vue'
export { default as ProviderDownloadModel } from './provider-download-model.vue'
export { default as ProviderSettingsContainer } from './provider-settings-container.vue'
export { default as ProviderSettingsLayout } from './provider-settings-layout.vue'
export { default as ProviderValidationAlerts } from './provider-validation-alerts.vue'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts">
import type { Availability } from 'xsai-chromium-prompt'

import { onMounted, ref } from 'vue'

const props = defineProps<{
label?: string
description?: string
checkAvailability: () => Promise<Availability>
download: (onProgress: (progress: number) => void) => Promise<Availability>
setVaild: () => void
}>()

const availability = ref('unavailable')
const progress = ref(0)

onMounted(async () => {
const availabilityNow = await props.checkAvailability()
availability.value = availabilityNow
if (availabilityNow === 'available') {
progress.value = 1
props.setVaild()
}
})

async function download() {
const resultAvailability = await props.download(p => progress.value = p)
availability.value = resultAvailability
Comment on lines +27 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle rejected model downloads

If the browser rejects the model download, such as after a network or storage failure, this click handler produces an unhandled promise rejection and leaves the control showing its stale availability and partial progress. Catch the failure and expose a recoverable error state so users know why the required setup did not complete.

Useful? React with 👍 / 👎.

}
Comment thread
AdairLi2504 marked this conversation as resolved.
Comment thread
AdairLi2504 marked this conversation as resolved.
</script>

<template>
<div class="max-w-full">
<label class="flex flex-col gap-4">
<div>
<div class="flex items-center gap-1 text-sm font-medium">
<slot name="label">
{{ props.label }}
</slot>
</div>
<div class="text-xs text-neutral-500 dark:text-neutral-400" text-wrap>
<slot name="description">
{{ props.description }}
</slot>
</div>
</div>
</label>
<div w-full flex flex-row gap-2>
<div w-full>
<div
class="flex items-center justify-between whitespace-nowrap rounded-lg bg-violet-100 px-3 py-2 text-sm dark:bg-violet-900"
:style="{ width: `${(progress * 100)}%` }"
>
<span>{{ availability }}</span>
Comment thread
AdairLi2504 marked this conversation as resolved.
<span>({{ (progress * 100).toFixed(2) }}%)</span>
</div>
</div>
<button rounded-lg bg="blue-100 dark:blue-900" px-4 py-2 @click="download">
Download
Comment thread
AdairLi2504 marked this conversation as resolved.
</button>
Comment on lines +58 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the shared button for the download action

This new control uses a raw HTML button instead of the standardized Button from @proj-airi/ui. It therefore bypasses the shared loading and disabled behavior that this async action needs, allowing repeated clicks to start overlapping downloads while also using inconsistent interaction and focus styling. Replace it with the shared button and bind its loading or disabled state while the download is active.

AGENTS.md reference: AGENTS.md:L94-L98

Useful? React with 👍 / 👎.

</div>
</div>
</template>
1 change: 1 addition & 0 deletions packages/stage-ui/src/libs/providers/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import './mimo-audio'
import './cloudflare-workers-ai'
import './azure-ai-foundry'
import './official'
import './prompt-api'
import './speech-noop'
import './unspeech'

Expand Down
83 changes: 83 additions & 0 deletions packages/stage-ui/src/libs/providers/providers/prompt-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { errorMessageFrom } from '@moeru/std'
import { checkPromptAvailability, createChatProvider } from 'xsai-chromium-prompt'
import { z } from 'zod'

import { defineProvider } from '../registry'

const openAICompatibleConfigSchema = z.object({
apiKey: z
.string('API Key')
.optional(),
baseUrl: z
.string('Base URL')
.optional()
.default('http://localhost:5173/settings/providers/chat/prompt-api'),
Comment thread
AdairLi2504 marked this conversation as resolved.
})

type OpenAICompatibleConfig = z.input<typeof openAICompatibleConfigSchema>

export const providerPromptAPICompatible = defineProvider<OpenAICompatibleConfig>({
id: 'prompt-api',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify Prompt API as free and local

When users select either the Free or Local provider filter, Prompt API disappears even though it is a free, browser-local provider. resolveProviderAttributes() returns no attributes for IDs absent from providerAttributesById (packages/stage-ui/src/libs/providers/attributes.ts:137-149), and the catalog rejects providers whose attribute does not equal the active filter (packages/stage-pages/src/pages/settings/providers/index.vue:175-181). Add prompt-api to the centralized attribute map as freeLocal.

Useful? React with 👍 / 👎.

name: 'Prompt API',
nameLocalize: ({ t }) => t('settings.pages.providers.provider.prompt-api.title'),
description: 'With the Prompt API, you can send natural language requests to the foundation model in Chrome.',
descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.prompt-api.description'),
tasks: ['chat'],
icon: 'i-simple-icons:googlechrome',
iconColor: 'i-logos:chrome',
Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark the local provider as credential-free

When VITE_DISABLE_CUSTOM_PROVIDERS=true, the provider store hides definitions whose requiresCredentials value is not false. This Prompt API uses the local browser model and needs no user credentials, but the missing flag classifies it as a custom remote provider. As a result, supported Chrome users cannot find it in these builds. Set requiresCredentials: false, as the other browser and local providers do.

Useful? React with 👍 / 👎.


Comment thread
AdairLi2504 marked this conversation as resolved.
createProviderConfig: () => openAICompatibleConfigSchema,
createProvider() {
return createChatProvider()
},

validationRequiredWhen(config) {
return !!config.apiKey?.trim()
},
Comment thread
AdairLi2504 marked this conversation as resolved.
Comment on lines +34 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run Prompt API validation without an API key

When a user adds Prompt API through /v2/settings/providers, the new provider starts with an empty configuration, so this predicate remains false because Prompt API never needs an API key. The v2 editor consequently exits runValidation() at packages/stage-pages/src/pages/v2/settings/providers/edit/[providerId]/index.vue:230-235, even when the user clicks Validate, and the provider remains unconfigured. Make readiness validation required independently of apiKey.

Useful? React with 👍 / 👎.

validators: {
validateConfig: [
({ t }) => ({
id: 'prompt-api:check-availability',
name: t('settings.pages.providers.catalog.edit.validators.prompt-api.check-availability.title'),
schedule: {
mode: 'interval',
intervalMs: 15_000,
},
Comment thread
AdairLi2504 marked this conversation as resolved.
validator: async () => {
const errors: Array<{ error: unknown }> = []
let reason = ''
try {
const availability = await checkPromptAvailability()
switch (availability) {
case 'available':
break
case 'downloadable':
reason = 'The model is downloadable'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize Prompt API validation failures

When a non-English locale is active and the model is downloadable, downloading, or unavailable, this reason is passed through useProviderValidation and displayed verbatim in ProviderValidationAlerts. Add translation keys for these validation results and populate reasonKey instead of exposing the new English-only messages.

AGENTS.md reference: AGENTS.md:L128-L131

Useful? React with 👍 / 👎.

errors.push({ error: new Error(reason) })
break
case 'downloading':
reason = 'The model is downloading'
errors.push({ error: new Error(reason) })
break
case 'unavailable':
reason = 'The Prompt API is unavailable'
errors.push({ error: new Error(reason) })
break
}
}
catch (e) {
const errorMessage = errorMessageFrom(e) || 'Unknown error.'
const reason = `Connectivity check failed: ${errorMessage}`
errors.push({ error: new Error(reason) })
}
return {
errors,
reason: errors.length > 0 ? errors.map(item => (item.error as Error).message).join(', ') : '',
reasonKey: '',
valid: errors.length === 0,
}
},
}),
],
},
})
35 changes: 35 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading