- Extract mapUpdate to testable module, dynamic import for @tauri-apps/plugin-shell - Add warnings for unhandled ACP content types and empty tool titles - MCP server: session limit (max 10), fix null WS comparison, guard JSX preprocessing - MCP server: read version from package.json instead of hardcoded 0.0.0 - MCP tests: use port 0 (OS-assigned) to prevent collision - Connection error handling with user-friendly messages, stale session recovery - Agent crash detection via close handler, destroying flag, buildCrashChunks - Port collision: detect EADDRINUSE in vite-plugin stderr and log clear error - Production Tauri: spawn openpencil-mcp via shell plugin, orphan reuse via health check - Permission confirmation dialog (reka-ui AlertDialog) with queue, 60s auto-reject timeout - Health check in ProviderSelect hides ACP agents when MCP server unavailable - Move DESIGN_CONTEXT to app constants, add ACP_PERMISSION_TIMEOUT_MS - 36 tests across 3 files (acp-transport, acp-permission, mcp-server) - Update CHANGELOG, README, CONTRIBUTING, AGENTS.md
121 lines
3.8 KiB
Vue
121 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
|
|
import ProviderSelect from '@/components/chat/ProviderSelect.vue'
|
|
import { uiInput } from '@/components/ui/input'
|
|
import { useAIChat } from '@/composables/use-chat'
|
|
import { ACP_AGENTS } from '@open-pencil/core'
|
|
|
|
const { providerID, providerDef, setAPIKey, customBaseURL, customModelID } = useAIChat()
|
|
|
|
const isACP = computed(() => providerID.value.startsWith('acp:'))
|
|
const acpAgent = computed(() => {
|
|
if (!isACP.value) return null
|
|
const id = providerID.value.replace('acp:', '')
|
|
return ACP_AGENTS.find((a) => a.id === id) ?? null
|
|
})
|
|
|
|
const keyInput = ref('')
|
|
const baseURLInput = ref(customBaseURL.value)
|
|
const customModelInput = ref(customModelID.value)
|
|
|
|
function save() {
|
|
const key = keyInput.value.trim()
|
|
if (!key) return
|
|
if (providerDef.value.supportsCustomBaseURL) {
|
|
customBaseURL.value = baseURLInput.value.trim()
|
|
}
|
|
if (providerDef.value.supportsCustomModel) {
|
|
customModelID.value = customModelInput.value.trim()
|
|
}
|
|
setAPIKey(key)
|
|
keyInput.value = ''
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div data-test-id="provider-setup" class="flex flex-1 flex-col items-center justify-center px-6">
|
|
<icon-lucide-sparkles class="mb-3 size-7 text-muted" />
|
|
<p class="mb-5 text-center text-xs text-muted">Connect an AI provider to start chatting.</p>
|
|
|
|
<form v-if="!isACP" class="flex w-full flex-col gap-2" @submit.prevent="save">
|
|
<ProviderSelect
|
|
test-id="provider-selector"
|
|
trigger-class="w-full justify-between rounded border border-border bg-input px-2.5 py-1.5 text-xs text-surface"
|
|
item-class="rounded px-2 py-1.5 text-[11px]"
|
|
/>
|
|
|
|
<!-- Base URL (OpenAI-compatible only) -->
|
|
<input
|
|
v-if="providerDef.supportsCustomBaseURL"
|
|
v-model="baseURLInput"
|
|
type="text"
|
|
data-test-id="provider-base-url"
|
|
placeholder="Base URL (e.g. http://localhost:11434/v1)"
|
|
:class="uiInput()"
|
|
/>
|
|
|
|
<!-- Custom model ID (OpenAI-compatible only) -->
|
|
<input
|
|
v-if="providerDef.supportsCustomModel"
|
|
v-model="customModelInput"
|
|
type="text"
|
|
data-test-id="provider-custom-model"
|
|
placeholder="Model ID (e.g. llama-3.3-70b)"
|
|
:class="uiInput()"
|
|
/>
|
|
|
|
<input
|
|
v-model="keyInput"
|
|
type="password"
|
|
data-test-id="api-key-input"
|
|
:placeholder="providerDef.keyPlaceholder"
|
|
:class="uiInput()"
|
|
/>
|
|
|
|
<button
|
|
type="submit"
|
|
data-test-id="api-key-save"
|
|
class="mt-1 w-full rounded bg-accent py-1.5 text-xs font-medium text-white hover:bg-accent/90"
|
|
:disabled="!keyInput.trim()"
|
|
>
|
|
Connect
|
|
</button>
|
|
</form>
|
|
|
|
<!-- ACP agent — no API key needed -->
|
|
<div v-else class="flex w-full flex-col gap-2">
|
|
<ProviderSelect
|
|
test-id="provider-selector"
|
|
trigger-class="w-full justify-between rounded border border-border bg-input px-2.5 py-1.5 text-xs text-surface"
|
|
item-class="rounded px-2 py-1.5 text-[11px]"
|
|
/>
|
|
|
|
<p class="text-center text-[10px] leading-relaxed text-muted">
|
|
Uses your existing {{ acpAgent?.name }} subscription. Make sure
|
|
<code class="rounded bg-input px-1 py-0.5 font-mono text-[9px]">{{
|
|
acpAgent?.command
|
|
}}</code>
|
|
is installed and authenticated.
|
|
</p>
|
|
</div>
|
|
|
|
<a
|
|
v-if="!isACP && providerDef.keyURL"
|
|
:href="providerDef.keyURL"
|
|
target="_blank"
|
|
data-test-id="api-key-get-link"
|
|
class="mt-2.5 text-[10px] text-muted underline hover:text-surface"
|
|
>
|
|
Get an {{ providerDef.name }} API key →
|
|
</a>
|
|
|
|
<p
|
|
v-if="providerID === 'openrouter'"
|
|
class="mt-3 text-center text-[10px] leading-relaxed text-muted/50"
|
|
>
|
|
One key for 100+ models from all providers.
|
|
</p>
|
|
</div>
|
|
</template>
|