feat: prioritize Routstr and expose website AI funding setup
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
<div v-else-if="mode === 'routstr'" class="space-y-3">
|
||||
<p class="text-sm text-white/70">Pay for AI with ecash. Add funds to this node’s wallet and set the most it may spend.</p>
|
||||
<p class="text-sm text-white/70">Ecash balance: {{ balance === null ? 'Unavailable' : balance.toLocaleString() + ' sats' }}</p>
|
||||
<button class="glass-button rounded-lg px-4 py-2 text-sm text-white" @click="funding = true">Add ecash</button>
|
||||
<button class="glass-button rounded-lg px-4 py-2 text-sm text-white" @click="funding = true">Top up with ecash</button>
|
||||
<RoutstrBudgetSection />
|
||||
<button class="glass-button rounded-lg px-4 py-2 text-sm text-white w-full" @click="continueRoutstr">Use Routstr</button>
|
||||
<p class="text-xs text-white/50">The selected model’s price and accepted mint still apply. No payment is sent by opening this setup.</p>
|
||||
@@ -49,8 +49,8 @@ interface ProviderStatus {
|
||||
}
|
||||
const props = defineProps<{ show: boolean }>()
|
||||
const emit = defineEmits<{ close: []; configured: [provider: Choice | 'auto' | 'local', model?: string] }>()
|
||||
const choices: { id: Choice; label: string }[] = [{ id: 'claude', label: 'Claude API' }, { id: 'openai', label: 'OpenAI API' }, { id: 'routstr', label: 'Routstr · sats' }]
|
||||
const mode = ref<Choice | null>(null)
|
||||
const choices: { id: Choice; label: string }[] = [{ id: 'routstr', label: 'Routstr · sats' }, { id: 'claude', label: 'Claude API' }, { id: 'openai', label: 'OpenAI API' }]
|
||||
const mode = ref<Choice | null>('routstr')
|
||||
const key = ref('')
|
||||
const model = ref('')
|
||||
const status = ref<ProviderStatus | null>(null)
|
||||
@@ -129,7 +129,7 @@ async function continueRoutstr() {
|
||||
} catch { error.value = 'Could not select Routstr. Try again.' }
|
||||
}
|
||||
async function finishFunding() { funding.value = false; await refreshBalance() }
|
||||
watch(() => props.show, open => { if (open) { error.value = ''; void refresh() } else { key.value = ''; funding.value = false } })
|
||||
watch(() => props.show, open => { if (open) { error.value = ''; void refresh(); if (mode.value === 'routstr') void refreshBalance() } else { key.value = ''; funding.value = false } })
|
||||
async function syncSelection() { const state = await refresh(); if (state) emit('configured', state.settings.provider, state.settings.provider === 'openai' ? state.settings.openai_model : undefined) }
|
||||
defineExpose({ checkNeeded, syncSelection, showRoutstr: () => choose('routstr') })
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import AIConnectionModal from '../AIConnectionModal.vue'
|
||||
import ReceiveBitcoinModal from '../ReceiveBitcoinModal.vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
vi.mock('@/api/rpc-client', () => ({ rpcClient: { call: vi.fn() } }))
|
||||
vi.mock('../ReceiveBitcoinModal.vue', () => ({ default: { props: ['show', 'initialMethod'], template: '<div />' } }))
|
||||
@@ -10,6 +11,16 @@ function mountModal() { return mount(AIConnectionModal, { props: { show: false }
|
||||
function button(w: ReturnType<typeof mountModal>, label: string) { return w.findAll('button').find(b => b.text() === label)! }
|
||||
beforeEach(() => { vi.clearAllMocks(); vi.mocked(rpcClient.call).mockImplementation(async ({ method }) => method === 'system.settings.get' ? { value: state() } : {}) })
|
||||
describe('AI connection setup', () => {
|
||||
it('opens on Routstr with a top-up action without authorizing spending', async () => {
|
||||
const w = mountModal(); await w.setProps({ show: true }); await flushPromises()
|
||||
expect(button(w, 'Routstr · sats').attributes('aria-pressed')).toBe('true')
|
||||
expect(button(w, 'Top up with ecash').exists()).toBe(true)
|
||||
expect(vi.mocked(rpcClient.call).mock.calls.every(([r]) => !['assistant.budget-set', 'system.settings.set'].includes(r.method))).toBe(true)
|
||||
await button(w, 'Top up with ecash').trigger('click')
|
||||
expect(w.findComponent(ReceiveBitcoinModal).props()).toMatchObject({ show: true, initialMethod: 'ecash' })
|
||||
w.unmount()
|
||||
})
|
||||
|
||||
it('detects absent configuration without treating a failed status query as missing keys', async () => {
|
||||
const w = mountModal()
|
||||
expect(await (w.vm as any).checkNeeded()).toBe(true)
|
||||
|
||||
Reference in New Issue
Block a user