AgentsPage.vue
3.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { Badge, Button, Card, Input } from '@drama/ui'
import { api, getToken } from '../api.js'
const { t } = useI18n()
const router = useRouter()
const agents = ref(null)
/** 每个 Agent 的页面状态:刚重置出的 key、上限输入框、提示。 */
const local = reactive({})
const BADGE = { pending_claim: 'neutral', active: 'success', suspended: 'warning' }
async function load() {
agents.value = (await api('GET', '/api/voko/agents')).agents
for (const agent of agents.value) {
if (local[agent.id] === undefined) local[agent.id] = { key: '', limit: agent.daily_spend_limit === null ? '' : String(agent.daily_spend_limit), message: '' }
}
}
/**
* @param {string} id
* @param {() => Promise<void>} action
*/
async function run(id, action) {
local[id].message = ''
try {
await action()
await load()
} catch (err) {
local[id].message = err instanceof Error ? err.message : String(err)
}
}
/** @param {string} id */
const resetKey = (id) => run(id, async () => {
local[id].key = (await api('POST', `/api/voko/agents/${id}/api-key/reset`)).api_key
})
/**
* @param {string} id
* @param {'active'|'suspended'} status
*/
const setStatus = (id, status) => run(id, async () => {
await api('POST', `/api/voko/agents/${id}/status`, { status })
})
/** @param {string} id */
const saveLimit = (id) => run(id, async () => {
const raw = String(local[id].limit).trim()
await api('POST', `/api/voko/agents/${id}/daily-spend-limit`, { daily_spend_limit: raw === '' ? null : Number(raw) })
local[id].message = t('agents.saved')
})
onMounted(async () => {
if (getToken() === null) {
await router.push({ path: '/login', query: { next: '/agents' } })
return
}
await load()
})
</script>
<template>
<h1>{{ t('agents.title') }}</h1>
<p><router-link to="/approvals">{{ t('agents.approvals') }}</router-link></p>
<template v-if="agents">
<p v-if="agents.length === 0">{{ t('agents.empty') }}</p>
<Card v-for="agent in agents" :key="agent.id" class="voko-agent">
<h2>{{ agent.name }} <Badge :variant="BADGE[agent.status]">{{ t(`agents.status.${agent.status}`) }}</Badge></h2>
<p>{{ t('agents.level', { level: agent.level }) }} · {{ t('agents.wallet', agent.wallet) }}</p>
<p><router-link :to="`/agents/${agent.id}/board`">{{ t('agents.board') }}</router-link></p>
<label class="voko-field">{{ t('agents.dailyLimit') }}<Input v-model="local[agent.id].limit" type="number" /></label>
<div class="voko-actions">
<Button variant="secondary" @click="saveLimit(agent.id)">{{ t('agents.saveLimit') }}</Button>
<Button variant="secondary" @click="resetKey(agent.id)">{{ t('agents.resetKey') }}</Button>
<Button v-if="agent.status === 'active'" variant="ghost" @click="setStatus(agent.id, 'suspended')">{{ t('agents.suspend') }}</Button>
<Button v-else-if="agent.status === 'suspended'" variant="ghost" @click="setStatus(agent.id, 'active')">{{ t('agents.resume') }}</Button>
</div>
<p v-if="local[agent.id].key">{{ t('agents.newKey') }} <code class="voko-key">{{ local[agent.id].key }}</code></p>
<p v-if="local[agent.id].message" role="status">{{ local[agent.id].message }}</p>
</Card>
</template>
</template>
<style scoped>
.voko-agent {
margin-bottom: var(--drama-space-md);
}
.voko-actions {
display: flex;
flex-wrap: wrap;
gap: var(--drama-space-sm);
}
.voko-key {
overflow-wrap: anywhere;
}
</style>