-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
feat(providers): add Prompt API Provider #2299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
1bc0a1e
54db883
47a2ae4
9f0edd5
c405e11
2453284
1bf9666
519ae69
ffb742c
eb9db74
0bc0b5c
b19e836
0924c16
6ec73f8
8ef4576
d487908
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| 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')" | ||
|
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')" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Prompt API is not already active, this callback only changes the route. The consciousness page loads models from the persisted Useful? React with 👍 / 👎. |
||
| /> | ||
| </ProviderSettingsContainer> | ||
| </ProviderSettingsLayout> | ||
| </template> | ||
|
|
||
| <route lang="yaml"> | ||
| meta: | ||
| layout: settings | ||
| stageTransition: | ||
| name: slide | ||
| </route> | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 👍 / 👎. |
||
| } | ||
|
AdairLi2504 marked this conversation as resolved.
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> | ||
|
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 | ||
|
AdairLi2504 marked this conversation as resolved.
|
||
| </button> | ||
|
Comment on lines
+58
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This new control uses a raw HTML button instead of the standardized AGENTS.md reference: AGENTS.md:L94-L98 Useful? React with 👍 / 👎. |
||
| </div> | ||
| </div> | ||
| </template> | ||
| 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'), | ||
|
AdairLi2504 marked this conversation as resolved.
|
||
| }) | ||
|
|
||
| type OpenAICompatibleConfig = z.input<typeof openAICompatibleConfigSchema> | ||
|
|
||
| export const providerPromptAPICompatible = defineProvider<OpenAICompatibleConfig>({ | ||
| id: 'prompt-api', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When users select either the Free or Local provider filter, Prompt API disappears even though it is a free, browser-local provider. 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
|
AdairLi2504 marked this conversation as resolved.
|
||
| createProviderConfig: () => openAICompatibleConfigSchema, | ||
| createProvider() { | ||
| return createChatProvider() | ||
| }, | ||
|
|
||
| validationRequiredWhen(config) { | ||
| return !!config.apiKey?.trim() | ||
| }, | ||
|
AdairLi2504 marked this conversation as resolved.
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a user adds Prompt API through 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, | ||
| }, | ||
|
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' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a non-English locale is active and the model is downloadable, downloading, or unavailable, this reason is passed through 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, | ||
| } | ||
| }, | ||
| }), | ||
| ], | ||
| }, | ||
| }) | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.