AgentsPage.vue
4.75 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<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)
const me = ref(null)
const note = ref('')
/** 每个 Agent 的页面状态:刚重置出的 key、上限输入框、提示。 */
const local = reactive({})
const BADGE = { pending_claim: 'neutral', active: 'success', suspended: 'warning' }
async function load() {
me.value = await api('GET', '/api/voko/me')
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: '' }
}
}
async function apply() {
await api('POST', '/api/voko/access-requests', { note: note.value })
await load()
}
/**
* @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 class="voko-nav"><router-link to="/help">{{ t('help.title') }}</router-link> <router-link to="/approvals">{{ t('agents.approvals') }}</router-link> <router-link to="/settings">{{ t('settings.title') }}</router-link></p>
<Card v-if="me && (me.access_request === null || me.access_request.status !== 'approved')" class="voko-agent">
<p>{{ me.user.email }}</p>
<template v-if="me.access_request === null">
<p>{{ t('home.notApplied') }}</p>
<label class="voko-field">{{ t('home.note') }}<Input v-model="note" /></label>
<Button @click="apply">{{ t('home.apply') }}</Button>
</template>
<p v-else>{{ t(`home.status.${me.access_request.status}`) }}</p>
</Card>
<p v-if="me && me.user.role === 'admin'"><router-link to="/admin">{{ t('home.admin') }}</router-link></p>
<template v-if="agents">
<p v-if="agents.length === 0 && me.access_request !== null && me.access_request.status === 'approved'">{{ 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 class="voko-nav"><router-link :to="`/agents/${agent.id}/world`">{{ t('world.title') }}</router-link> <router-link :to="`/agents/${agent.id}/board`">{{ t('agents.board') }}</router-link> <router-link :to="`/agents/${agent.id}/ledger`">{{ t('ledger.title') }}</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-nav {
display: flex;
flex-wrap: wrap;
gap: var(--drama-space-md);
}
.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>