arkaos 3.70.5 → 3.70.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/dashboard/app/pages/terminal.vue +93 -77
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/dashboard/app/pages/personas.vue +0 -719
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.70.
|
|
1
|
+
3.70.7
|
|
@@ -101,10 +101,17 @@ watch(searchResults, () => {
|
|
|
101
101
|
searchSelectedIdx.value = 0
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
+
const searchInputEl = ref<HTMLInputElement | null>(null)
|
|
105
|
+
|
|
104
106
|
function openSearch() {
|
|
105
107
|
searchOpen.value = true
|
|
106
108
|
searchQuery.value = ''
|
|
107
109
|
searchSelectedIdx.value = 0
|
|
110
|
+
// autofocus on the bare input only fires on initial mount; the modal
|
|
111
|
+
// is mounted persistently, so we focus explicitly each time it opens.
|
|
112
|
+
nextTick(() => {
|
|
113
|
+
requestAnimationFrame(() => searchInputEl.value?.focus())
|
|
114
|
+
})
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
function pickFromSearch(cmd: string) {
|
|
@@ -392,22 +399,18 @@ const showHistory = ref(false)
|
|
|
392
399
|
No matches for
|
|
393
400
|
<span class="font-mono text-default">{{ sidebarFilter }}</span>.
|
|
394
401
|
</div>
|
|
395
|
-
<ul v-else class="
|
|
402
|
+
<ul v-else class="py-1">
|
|
396
403
|
<li
|
|
397
404
|
v-for="entry in visibleHistory"
|
|
398
405
|
:key="entry.ts"
|
|
399
|
-
class="group px-
|
|
406
|
+
class="group mx-1 px-2.5 py-1 rounded-md cursor-pointer flex items-center gap-2 hover:bg-elevated/40 transition-colors"
|
|
400
407
|
:title="`${entry.cmd} — ${relativeTime(entry.ts)}`"
|
|
401
408
|
@click="sendToActive(entry.cmd)"
|
|
402
409
|
>
|
|
403
|
-
<UIcon
|
|
404
|
-
name="i-lucide-chevron-right"
|
|
405
|
-
class="size-3 shrink-0 text-muted group-hover:text-primary"
|
|
406
|
-
/>
|
|
407
410
|
<span class="flex-1 min-w-0 font-mono text-xs truncate">
|
|
408
411
|
{{ entry.cmd }}
|
|
409
412
|
</span>
|
|
410
|
-
<span class="text-[10px] text-muted shrink-0 tabular-nums opacity-0 group-hover:opacity-100 transition-opacity">
|
|
413
|
+
<span class="text-[10px] text-muted/70 shrink-0 tabular-nums opacity-0 group-hover:opacity-100 transition-opacity">
|
|
411
414
|
{{ relativeTime(entry.ts) }}
|
|
412
415
|
</span>
|
|
413
416
|
<UIcon
|
|
@@ -431,112 +434,125 @@ const showHistory = ref(false)
|
|
|
431
434
|
|
|
432
435
|
<UModal
|
|
433
436
|
v-model:open="searchOpen"
|
|
434
|
-
:ui="{ content: 'max-w-2xl' }"
|
|
437
|
+
:ui="{ content: 'max-w-2xl ring-0 shadow-2xl' }"
|
|
435
438
|
>
|
|
436
439
|
<template #content>
|
|
437
|
-
<
|
|
438
|
-
<
|
|
439
|
-
<
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
esc
|
|
454
|
-
</kbd>
|
|
455
|
-
</div>
|
|
456
|
-
</template>
|
|
440
|
+
<div class="rounded-xl bg-default overflow-hidden">
|
|
441
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-default/60">
|
|
442
|
+
<UIcon name="i-lucide-history" class="size-4 text-muted shrink-0" />
|
|
443
|
+
<input
|
|
444
|
+
ref="searchInputEl"
|
|
445
|
+
v-model="searchQuery"
|
|
446
|
+
type="text"
|
|
447
|
+
autofocus
|
|
448
|
+
placeholder="Filter command history…"
|
|
449
|
+
class="palette-input flex-1 bg-transparent text-default placeholder:text-muted/70 focus:outline-none border-0 ring-0 text-sm"
|
|
450
|
+
@keydown="searchKeydown"
|
|
451
|
+
>
|
|
452
|
+
<span class="text-[11px] text-muted/70 shrink-0 tabular-nums">
|
|
453
|
+
{{ searchResults.length }} of {{ history.length }}
|
|
454
|
+
</span>
|
|
455
|
+
</div>
|
|
457
456
|
|
|
458
457
|
<div class="max-h-[60vh] overflow-y-auto">
|
|
459
458
|
<div
|
|
460
459
|
v-if="history.length === 0"
|
|
461
|
-
class="
|
|
460
|
+
class="px-6 py-12 text-center text-sm text-muted"
|
|
462
461
|
>
|
|
463
|
-
<UIcon name="i-lucide-terminal" class="size-
|
|
462
|
+
<UIcon name="i-lucide-terminal" class="size-7 mx-auto mb-3 opacity-30" />
|
|
464
463
|
<p>No commands yet.</p>
|
|
465
|
-
<p class="text-xs mt-1">
|
|
464
|
+
<p class="text-xs mt-1 opacity-70">
|
|
466
465
|
Run something in the terminal — it'll show up here.
|
|
467
466
|
</p>
|
|
468
467
|
</div>
|
|
469
468
|
<div
|
|
470
469
|
v-else-if="searchResults.length === 0"
|
|
471
|
-
class="
|
|
470
|
+
class="px-6 py-12 text-center text-sm text-muted"
|
|
472
471
|
>
|
|
473
472
|
No match for
|
|
474
473
|
<span class="font-mono text-default">{{ searchQuery }}</span>.
|
|
475
474
|
</div>
|
|
476
|
-
<ul v-else class="
|
|
475
|
+
<ul v-else class="py-1">
|
|
477
476
|
<li
|
|
478
477
|
v-for="(entry, i) in searchResults"
|
|
479
478
|
:key="entry.ts"
|
|
480
|
-
class="px-
|
|
479
|
+
class="mx-1 px-3 py-1.5 rounded-md cursor-pointer flex items-center gap-3 transition-colors"
|
|
481
480
|
:class="i === searchSelectedIdx
|
|
482
|
-
? 'bg-
|
|
483
|
-
: 'hover:bg-elevated/
|
|
481
|
+
? 'bg-elevated/70'
|
|
482
|
+
: 'hover:bg-elevated/30'"
|
|
484
483
|
@click="pickFromSearch(entry.cmd)"
|
|
485
484
|
@mouseenter="searchSelectedIdx = i"
|
|
486
485
|
>
|
|
487
|
-
<UIcon
|
|
488
|
-
name="i-lucide-chevron-right"
|
|
489
|
-
class="size-3.5 shrink-0"
|
|
490
|
-
:class="i === searchSelectedIdx ? 'text-primary' : 'text-muted'"
|
|
491
|
-
/>
|
|
492
486
|
<span class="flex-1 min-w-0 font-mono text-sm truncate">
|
|
493
487
|
{{ entry.cmd }}
|
|
494
488
|
</span>
|
|
495
|
-
<span class="text-
|
|
489
|
+
<span class="text-[11px] text-muted/70 shrink-0 tabular-nums">
|
|
496
490
|
{{ relativeTime(entry.ts) }}
|
|
497
491
|
</span>
|
|
498
|
-
<
|
|
499
|
-
|
|
500
|
-
class="
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
</kbd>
|
|
492
|
+
<UIcon
|
|
493
|
+
name="i-lucide-corner-down-left"
|
|
494
|
+
class="size-3.5 shrink-0 transition-opacity"
|
|
495
|
+
:class="i === searchSelectedIdx ? 'text-default opacity-100' : 'text-muted opacity-0'"
|
|
496
|
+
/>
|
|
504
497
|
</li>
|
|
505
498
|
</ul>
|
|
506
499
|
</div>
|
|
507
500
|
|
|
508
|
-
<
|
|
509
|
-
<
|
|
510
|
-
<
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
</
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
>
|
|
532
|
-
Clear all
|
|
533
|
-
</UButton>
|
|
534
|
-
</div>
|
|
535
|
-
</template>
|
|
536
|
-
</UCard>
|
|
501
|
+
<div class="px-4 py-2.5 border-t border-default/60 flex items-center gap-4 text-[11px] text-muted/80">
|
|
502
|
+
<span class="flex items-center gap-1.5">
|
|
503
|
+
<kbd class="palette-kbd">↑</kbd><kbd class="palette-kbd">↓</kbd>
|
|
504
|
+
navigate
|
|
505
|
+
</span>
|
|
506
|
+
<span class="flex items-center gap-1.5">
|
|
507
|
+
<kbd class="palette-kbd">↵</kbd>
|
|
508
|
+
send
|
|
509
|
+
</span>
|
|
510
|
+
<span class="flex items-center gap-1.5">
|
|
511
|
+
<kbd class="palette-kbd">esc</kbd>
|
|
512
|
+
close
|
|
513
|
+
</span>
|
|
514
|
+
<button
|
|
515
|
+
v-if="history.length > 0"
|
|
516
|
+
class="ml-auto text-muted/80 hover:text-red-400 transition-colors flex items-center gap-1"
|
|
517
|
+
@click="clearHistory"
|
|
518
|
+
>
|
|
519
|
+
<UIcon name="i-lucide-trash-2" class="size-3" />
|
|
520
|
+
Clear
|
|
521
|
+
</button>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
537
524
|
</template>
|
|
538
525
|
</UModal>
|
|
539
526
|
</div>
|
|
540
527
|
</template>
|
|
541
528
|
</UDashboardPanel>
|
|
542
529
|
</template>
|
|
530
|
+
|
|
531
|
+
<style scoped>
|
|
532
|
+
.palette-input {
|
|
533
|
+
/* Defensive: kill any inherited ring/border from Tailwind base */
|
|
534
|
+
box-shadow: none !important;
|
|
535
|
+
outline: none !important;
|
|
536
|
+
}
|
|
537
|
+
.palette-input:focus,
|
|
538
|
+
.palette-input:focus-visible {
|
|
539
|
+
outline: none !important;
|
|
540
|
+
box-shadow: none !important;
|
|
541
|
+
border-color: transparent !important;
|
|
542
|
+
}
|
|
543
|
+
.palette-kbd {
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
justify-content: center;
|
|
547
|
+
min-width: 1.1rem;
|
|
548
|
+
padding: 0 0.3rem;
|
|
549
|
+
height: 1.1rem;
|
|
550
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
551
|
+
font-size: 10px;
|
|
552
|
+
line-height: 1;
|
|
553
|
+
border-radius: 4px;
|
|
554
|
+
background-color: rgb(var(--ui-bg-elevated) / 0.5);
|
|
555
|
+
color: rgb(var(--ui-text-muted));
|
|
556
|
+
border: 1px solid rgb(var(--ui-border) / 0.4);
|
|
557
|
+
}
|
|
558
|
+
</style>
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,719 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { Persona } from '~/types'
|
|
3
|
-
|
|
4
|
-
const { fetchApi, apiBase } = useApi()
|
|
5
|
-
const toast = useToast()
|
|
6
|
-
|
|
7
|
-
// --- Fetch personas (no await — non-blocking) ---
|
|
8
|
-
const { data, status, error, refresh } = fetchApi<{ personas: Persona[]; total: number }>('/api/personas')
|
|
9
|
-
|
|
10
|
-
const personas = computed(() => data.value?.personas ?? [])
|
|
11
|
-
|
|
12
|
-
// PR74 v2.92.0 — detail/edit drawer state
|
|
13
|
-
const detailOpen = ref(false)
|
|
14
|
-
const detailPersonaId = ref<string | null>(null)
|
|
15
|
-
|
|
16
|
-
// PR77 v2.95.0 — reverse-usage (which agents link to each persona).
|
|
17
|
-
const { data: usageData } = fetchApi<{
|
|
18
|
-
by_persona: Record<string, { agent_count: number, agent_ids: string[] }>
|
|
19
|
-
}>('/api/personas/usage')
|
|
20
|
-
|
|
21
|
-
function personaAgentCount(personaId: string): number {
|
|
22
|
-
return usageData.value?.by_persona?.[personaId]?.agent_count ?? 0
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function mbtiGradient(mbti: string | undefined): string {
|
|
26
|
-
if (!mbti) return 'bg-gradient-to-br from-muted/20 to-muted/5'
|
|
27
|
-
const code = mbti.toUpperCase()
|
|
28
|
-
if (['INTJ', 'INTP', 'ENTJ', 'ENTP'].includes(code))
|
|
29
|
-
return 'bg-gradient-to-br from-blue-500/25 to-indigo-600/10'
|
|
30
|
-
if (['INFJ', 'INFP', 'ENFJ', 'ENFP'].includes(code))
|
|
31
|
-
return 'bg-gradient-to-br from-emerald-500/25 to-teal-600/10'
|
|
32
|
-
if (['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'].includes(code))
|
|
33
|
-
return 'bg-gradient-to-br from-amber-500/25 to-orange-600/10'
|
|
34
|
-
if (['ISTP', 'ISFP', 'ESTP', 'ESFP'].includes(code))
|
|
35
|
-
return 'bg-gradient-to-br from-rose-500/25 to-pink-600/10'
|
|
36
|
-
return 'bg-gradient-to-br from-primary/20 to-primary/5'
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function personaInitials(name: string): string {
|
|
40
|
-
if (!name) return '·'
|
|
41
|
-
const parts = name.trim().split(/\s+/)
|
|
42
|
-
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()
|
|
43
|
-
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function openDetail(persona: Persona) {
|
|
47
|
-
detailPersonaId.value = persona.id
|
|
48
|
-
detailOpen.value = true
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function onDetailSaved() {
|
|
52
|
-
await refresh()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function onDetailDeleted(_id: string) {
|
|
56
|
-
await refresh()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// --- Creation mode ---
|
|
60
|
-
// PR62 v2.79.0 — three modes: list (default), wizard (AI builder), manual.
|
|
61
|
-
// The wizard is the new primary path; manual stays as fallback for
|
|
62
|
-
// operators who want to type every DNA field by hand.
|
|
63
|
-
type CreateMode = 'list' | 'wizard' | 'manual'
|
|
64
|
-
const createMode = ref<CreateMode>('list')
|
|
65
|
-
const showForm = computed(() => createMode.value === 'manual')
|
|
66
|
-
|
|
67
|
-
function startWizard() {
|
|
68
|
-
createMode.value = 'wizard'
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function startManual() {
|
|
72
|
-
createMode.value = 'manual'
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function cancelCreation() {
|
|
76
|
-
createMode.value = 'list'
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function onWizardComplete() {
|
|
80
|
-
createMode.value = 'list'
|
|
81
|
-
await refresh()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// --- Form state ---
|
|
85
|
-
function defaultForm() {
|
|
86
|
-
return {
|
|
87
|
-
name: '',
|
|
88
|
-
title: '',
|
|
89
|
-
source: '',
|
|
90
|
-
tagline: '',
|
|
91
|
-
mbti: '',
|
|
92
|
-
disc_primary: '',
|
|
93
|
-
disc_secondary: '',
|
|
94
|
-
enneagram_type: '',
|
|
95
|
-
enneagram_wing: '',
|
|
96
|
-
big_five_o: 50,
|
|
97
|
-
big_five_c: 50,
|
|
98
|
-
big_five_e: 50,
|
|
99
|
-
big_five_a: 50,
|
|
100
|
-
big_five_n: 50,
|
|
101
|
-
mental_models: '',
|
|
102
|
-
expertise_domains: '',
|
|
103
|
-
frameworks: '',
|
|
104
|
-
communication_tone: '',
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const form = ref(defaultForm())
|
|
109
|
-
const creating = ref(false)
|
|
110
|
-
|
|
111
|
-
// --- Options ---
|
|
112
|
-
const mbtiTypes = [
|
|
113
|
-
'INTJ', 'INTP', 'ENTJ', 'ENTP',
|
|
114
|
-
'INFJ', 'INFP', 'ENFJ', 'ENFP',
|
|
115
|
-
'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ',
|
|
116
|
-
'ISTP', 'ISFP', 'ESTP', 'ESFP',
|
|
117
|
-
].map(t => ({ label: t, value: t }))
|
|
118
|
-
|
|
119
|
-
const discTypes = [
|
|
120
|
-
{ label: 'D — Dominance', value: 'D' },
|
|
121
|
-
{ label: 'I — Influence', value: 'I' },
|
|
122
|
-
{ label: 'S — Steadiness', value: 'S' },
|
|
123
|
-
{ label: 'C — Conscientiousness', value: 'C' },
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
const enneagramTypes = Array.from({ length: 9 }, (_, i) => ({
|
|
127
|
-
label: `Type ${i + 1}`,
|
|
128
|
-
value: String(i + 1),
|
|
129
|
-
}))
|
|
130
|
-
|
|
131
|
-
const departmentOptions = [
|
|
132
|
-
'dev', 'marketing', 'brand', 'finance', 'strategy',
|
|
133
|
-
'ecom', 'kb', 'ops', 'pm', 'saas',
|
|
134
|
-
'landing', 'content', 'community', 'sales', 'leadership', 'org',
|
|
135
|
-
].map(d => ({ label: d, value: d }))
|
|
136
|
-
|
|
137
|
-
const tierOptions = [
|
|
138
|
-
{ label: 'Tier 1 — Squad Leads', value: '1' },
|
|
139
|
-
{ label: 'Tier 2 — Specialists', value: '2' },
|
|
140
|
-
{ label: 'Tier 3 — Support', value: '3' },
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
// --- Create persona ---
|
|
144
|
-
async function createPersona() {
|
|
145
|
-
if (!form.value.name.trim()) return
|
|
146
|
-
|
|
147
|
-
creating.value = true
|
|
148
|
-
try {
|
|
149
|
-
await $fetch(`${apiBase}/api/personas`, {
|
|
150
|
-
method: 'POST',
|
|
151
|
-
body: {
|
|
152
|
-
name: form.value.name,
|
|
153
|
-
title: form.value.title,
|
|
154
|
-
source: form.value.source,
|
|
155
|
-
tagline: form.value.tagline,
|
|
156
|
-
mbti: form.value.mbti,
|
|
157
|
-
disc: {
|
|
158
|
-
primary: form.value.disc_primary,
|
|
159
|
-
secondary: form.value.disc_secondary,
|
|
160
|
-
},
|
|
161
|
-
enneagram: {
|
|
162
|
-
type: form.value.enneagram_type ? Number(form.value.enneagram_type) : null,
|
|
163
|
-
wing: form.value.enneagram_wing ? Number(form.value.enneagram_wing) : null,
|
|
164
|
-
},
|
|
165
|
-
big_five: {
|
|
166
|
-
openness: form.value.big_five_o,
|
|
167
|
-
conscientiousness: form.value.big_five_c,
|
|
168
|
-
extraversion: form.value.big_five_e,
|
|
169
|
-
agreeableness: form.value.big_five_a,
|
|
170
|
-
neuroticism: form.value.big_five_n,
|
|
171
|
-
},
|
|
172
|
-
mental_models: form.value.mental_models
|
|
173
|
-
? form.value.mental_models.split(',').map(s => s.trim()).filter(Boolean)
|
|
174
|
-
: [],
|
|
175
|
-
expertise_domains: form.value.expertise_domains
|
|
176
|
-
? form.value.expertise_domains.split(',').map(s => s.trim()).filter(Boolean)
|
|
177
|
-
: [],
|
|
178
|
-
frameworks: form.value.frameworks
|
|
179
|
-
? form.value.frameworks.split(',').map(s => s.trim()).filter(Boolean)
|
|
180
|
-
: [],
|
|
181
|
-
communication: {
|
|
182
|
-
tone: form.value.communication_tone,
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
toast.add({ title: 'Persona created', description: `${form.value.name} has been added.`, color: 'success' })
|
|
188
|
-
form.value = defaultForm()
|
|
189
|
-
showForm.value = false
|
|
190
|
-
await refresh()
|
|
191
|
-
} catch {
|
|
192
|
-
toast.add({ title: 'Error', description: 'Failed to create persona.', color: 'error' })
|
|
193
|
-
} finally {
|
|
194
|
-
creating.value = false
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// --- Delete persona ---
|
|
199
|
-
const deleting = ref<string | null>(null)
|
|
200
|
-
|
|
201
|
-
async function deletePersona(persona: Persona) {
|
|
202
|
-
deleting.value = persona.id
|
|
203
|
-
try {
|
|
204
|
-
await $fetch(`${apiBase}/api/personas/${persona.id}`, { method: 'DELETE' })
|
|
205
|
-
toast.add({ title: 'Persona deleted', description: `${persona.name} has been removed.`, color: 'success' })
|
|
206
|
-
await refresh()
|
|
207
|
-
} catch {
|
|
208
|
-
toast.add({ title: 'Error', description: 'Failed to delete persona.', color: 'error' })
|
|
209
|
-
} finally {
|
|
210
|
-
deleting.value = null
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// --- Clone to agent (inline expansion, no modal) ---
|
|
215
|
-
const cloneExpandedId = ref<string | null>(null)
|
|
216
|
-
const cloneDepartment = ref('')
|
|
217
|
-
const cloneTier = ref('')
|
|
218
|
-
const cloning = ref(false)
|
|
219
|
-
|
|
220
|
-
function toggleClone(persona: Persona) {
|
|
221
|
-
if (cloneExpandedId.value === persona.id) {
|
|
222
|
-
cloneExpandedId.value = null
|
|
223
|
-
} else {
|
|
224
|
-
cloneExpandedId.value = persona.id
|
|
225
|
-
cloneDepartment.value = ''
|
|
226
|
-
cloneTier.value = ''
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function cloneToAgent(persona: Persona) {
|
|
231
|
-
if (!cloneDepartment.value || !cloneTier.value) return
|
|
232
|
-
|
|
233
|
-
cloning.value = true
|
|
234
|
-
try {
|
|
235
|
-
await $fetch(`${apiBase}/api/personas/${persona.id}/clone`, {
|
|
236
|
-
method: 'POST',
|
|
237
|
-
body: {
|
|
238
|
-
department: cloneDepartment.value,
|
|
239
|
-
tier: Number(cloneTier.value),
|
|
240
|
-
},
|
|
241
|
-
})
|
|
242
|
-
toast.add({
|
|
243
|
-
title: 'Agent created',
|
|
244
|
-
description: `${persona.name} cloned to ${cloneDepartment.value} department.`,
|
|
245
|
-
color: 'success',
|
|
246
|
-
})
|
|
247
|
-
cloneExpandedId.value = null
|
|
248
|
-
await refresh()
|
|
249
|
-
} catch {
|
|
250
|
-
toast.add({ title: 'Error', description: 'Failed to clone persona to agent.', color: 'error' })
|
|
251
|
-
} finally {
|
|
252
|
-
cloning.value = false
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// --- DNA badge colors ---
|
|
257
|
-
function mbtiColor(mbti: string): string {
|
|
258
|
-
if (!mbti) return 'neutral'
|
|
259
|
-
const analysts = ['INTJ', 'INTP', 'ENTJ', 'ENTP']
|
|
260
|
-
const diplomats = ['INFJ', 'INFP', 'ENFJ', 'ENFP']
|
|
261
|
-
const sentinels = ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ']
|
|
262
|
-
if (analysts.includes(mbti)) return 'primary'
|
|
263
|
-
if (diplomats.includes(mbti)) return 'success'
|
|
264
|
-
if (sentinels.includes(mbti)) return 'warning'
|
|
265
|
-
return 'error'
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function discColor(disc: string): string {
|
|
269
|
-
const colors: Record<string, string> = { D: 'error', I: 'warning', S: 'success', C: 'primary' }
|
|
270
|
-
return colors[disc] ?? 'neutral'
|
|
271
|
-
}
|
|
272
|
-
</script>
|
|
273
|
-
|
|
274
|
-
<template>
|
|
275
|
-
<UDashboardPanel id="personas">
|
|
276
|
-
<template #header>
|
|
277
|
-
<UDashboardNavbar title="Personas">
|
|
278
|
-
<template #leading>
|
|
279
|
-
<UDashboardSidebarCollapse />
|
|
280
|
-
</template>
|
|
281
|
-
|
|
282
|
-
<template #trailing>
|
|
283
|
-
<UBadge v-if="data?.total" :label="data.total" variant="subtle" />
|
|
284
|
-
</template>
|
|
285
|
-
|
|
286
|
-
<template #right>
|
|
287
|
-
<UButton
|
|
288
|
-
v-if="createMode === 'list'"
|
|
289
|
-
label="AI Builder"
|
|
290
|
-
icon="i-lucide-sparkles"
|
|
291
|
-
color="primary"
|
|
292
|
-
size="sm"
|
|
293
|
-
@click="startWizard"
|
|
294
|
-
/>
|
|
295
|
-
<UButton
|
|
296
|
-
v-if="createMode === 'list'"
|
|
297
|
-
label="Manual"
|
|
298
|
-
icon="i-lucide-plus"
|
|
299
|
-
variant="outline"
|
|
300
|
-
size="sm"
|
|
301
|
-
class="ml-2"
|
|
302
|
-
@click="startManual"
|
|
303
|
-
/>
|
|
304
|
-
<UButton
|
|
305
|
-
v-else
|
|
306
|
-
label="Back to list"
|
|
307
|
-
icon="i-lucide-arrow-left"
|
|
308
|
-
variant="ghost"
|
|
309
|
-
size="sm"
|
|
310
|
-
@click="cancelCreation"
|
|
311
|
-
/>
|
|
312
|
-
</template>
|
|
313
|
-
</UDashboardNavbar>
|
|
314
|
-
</template>
|
|
315
|
-
|
|
316
|
-
<template #body>
|
|
317
|
-
<div class="overflow-y-auto h-[calc(100vh-4rem)]">
|
|
318
|
-
<!-- Loading -->
|
|
319
|
-
<div v-if="status === 'pending'" class="flex items-center justify-center py-12" aria-label="Loading personas">
|
|
320
|
-
<UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
<!-- Error -->
|
|
324
|
-
<div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
|
|
325
|
-
<UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
|
|
326
|
-
<p class="text-sm text-muted">Failed to load personas.</p>
|
|
327
|
-
<UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
328
|
-
</div>
|
|
329
|
-
|
|
330
|
-
<!-- Content -->
|
|
331
|
-
<template v-else>
|
|
332
|
-
<!-- PR62: AI Persona Wizard -->
|
|
333
|
-
<PersonaWizard
|
|
334
|
-
v-if="createMode === 'wizard'"
|
|
335
|
-
class="mb-8"
|
|
336
|
-
@completed="onWizardComplete"
|
|
337
|
-
@cancelled="cancelCreation"
|
|
338
|
-
/>
|
|
339
|
-
|
|
340
|
-
<!-- Manual create form (legacy / fallback) -->
|
|
341
|
-
<UCard v-if="showForm" class="mb-8">
|
|
342
|
-
<form @submit.prevent="createPersona" class="space-y-8 p-2">
|
|
343
|
-
<!-- Identity -->
|
|
344
|
-
<fieldset>
|
|
345
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Identity</legend>
|
|
346
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
347
|
-
<UFormField label="Name" required>
|
|
348
|
-
<UInput
|
|
349
|
-
v-model="form.name"
|
|
350
|
-
placeholder="e.g. Alex Hormozi"
|
|
351
|
-
aria-label="Persona name"
|
|
352
|
-
class="w-full"
|
|
353
|
-
required
|
|
354
|
-
/>
|
|
355
|
-
</UFormField>
|
|
356
|
-
<UFormField label="Title">
|
|
357
|
-
<UInput
|
|
358
|
-
v-model="form.title"
|
|
359
|
-
placeholder="e.g. Business Strategy"
|
|
360
|
-
aria-label="Persona title"
|
|
361
|
-
class="w-full"
|
|
362
|
-
/>
|
|
363
|
-
</UFormField>
|
|
364
|
-
<UFormField label="Source">
|
|
365
|
-
<UInput
|
|
366
|
-
v-model="form.source"
|
|
367
|
-
placeholder="e.g. Alex Hormozi"
|
|
368
|
-
aria-label="Persona source"
|
|
369
|
-
class="w-full"
|
|
370
|
-
/>
|
|
371
|
-
</UFormField>
|
|
372
|
-
<UFormField label="Tagline">
|
|
373
|
-
<UInput
|
|
374
|
-
v-model="form.tagline"
|
|
375
|
-
placeholder="e.g. The Natural Commander"
|
|
376
|
-
aria-label="Persona tagline"
|
|
377
|
-
class="w-full"
|
|
378
|
-
/>
|
|
379
|
-
</UFormField>
|
|
380
|
-
</div>
|
|
381
|
-
</fieldset>
|
|
382
|
-
|
|
383
|
-
<!-- Behavioral DNA -->
|
|
384
|
-
<fieldset>
|
|
385
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Behavioral DNA</legend>
|
|
386
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
387
|
-
<UFormField label="MBTI">
|
|
388
|
-
<USelect
|
|
389
|
-
v-model="form.mbti"
|
|
390
|
-
:items="mbtiTypes"
|
|
391
|
-
placeholder="Select MBTI"
|
|
392
|
-
aria-label="MBTI type"
|
|
393
|
-
class="w-full"
|
|
394
|
-
/>
|
|
395
|
-
</UFormField>
|
|
396
|
-
<UFormField label="DISC Primary">
|
|
397
|
-
<USelect
|
|
398
|
-
v-model="form.disc_primary"
|
|
399
|
-
:items="discTypes"
|
|
400
|
-
placeholder="Primary"
|
|
401
|
-
aria-label="DISC primary type"
|
|
402
|
-
class="w-full"
|
|
403
|
-
/>
|
|
404
|
-
</UFormField>
|
|
405
|
-
<UFormField label="DISC Secondary">
|
|
406
|
-
<USelect
|
|
407
|
-
v-model="form.disc_secondary"
|
|
408
|
-
:items="discTypes"
|
|
409
|
-
placeholder="Secondary"
|
|
410
|
-
aria-label="DISC secondary type"
|
|
411
|
-
class="w-full"
|
|
412
|
-
/>
|
|
413
|
-
</UFormField>
|
|
414
|
-
<UFormField label="Enneagram Type">
|
|
415
|
-
<USelect
|
|
416
|
-
v-model="form.enneagram_type"
|
|
417
|
-
:items="enneagramTypes"
|
|
418
|
-
placeholder="Type"
|
|
419
|
-
aria-label="Enneagram type"
|
|
420
|
-
class="w-full"
|
|
421
|
-
/>
|
|
422
|
-
</UFormField>
|
|
423
|
-
</div>
|
|
424
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
425
|
-
<UFormField label="Enneagram Wing (1-9)">
|
|
426
|
-
<UInput
|
|
427
|
-
v-model="form.enneagram_wing"
|
|
428
|
-
type="number"
|
|
429
|
-
:min="1"
|
|
430
|
-
:max="9"
|
|
431
|
-
placeholder="e.g. 4"
|
|
432
|
-
aria-label="Enneagram wing"
|
|
433
|
-
class="w-full"
|
|
434
|
-
/>
|
|
435
|
-
</UFormField>
|
|
436
|
-
</div>
|
|
437
|
-
</fieldset>
|
|
438
|
-
|
|
439
|
-
<!-- Big Five -->
|
|
440
|
-
<fieldset>
|
|
441
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Big Five / OCEAN (0-100)</legend>
|
|
442
|
-
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
443
|
-
<UFormField label="Openness">
|
|
444
|
-
<UInput
|
|
445
|
-
v-model.number="form.big_five_o"
|
|
446
|
-
type="number"
|
|
447
|
-
:min="0"
|
|
448
|
-
:max="100"
|
|
449
|
-
aria-label="Openness score"
|
|
450
|
-
class="w-full"
|
|
451
|
-
/>
|
|
452
|
-
</UFormField>
|
|
453
|
-
<UFormField label="Conscientiousness">
|
|
454
|
-
<UInput
|
|
455
|
-
v-model.number="form.big_five_c"
|
|
456
|
-
type="number"
|
|
457
|
-
:min="0"
|
|
458
|
-
:max="100"
|
|
459
|
-
aria-label="Conscientiousness score"
|
|
460
|
-
class="w-full"
|
|
461
|
-
/>
|
|
462
|
-
</UFormField>
|
|
463
|
-
<UFormField label="Extraversion">
|
|
464
|
-
<UInput
|
|
465
|
-
v-model.number="form.big_five_e"
|
|
466
|
-
type="number"
|
|
467
|
-
:min="0"
|
|
468
|
-
:max="100"
|
|
469
|
-
aria-label="Extraversion score"
|
|
470
|
-
class="w-full"
|
|
471
|
-
/>
|
|
472
|
-
</UFormField>
|
|
473
|
-
<UFormField label="Agreeableness">
|
|
474
|
-
<UInput
|
|
475
|
-
v-model.number="form.big_five_a"
|
|
476
|
-
type="number"
|
|
477
|
-
:min="0"
|
|
478
|
-
:max="100"
|
|
479
|
-
aria-label="Agreeableness score"
|
|
480
|
-
class="w-full"
|
|
481
|
-
/>
|
|
482
|
-
</UFormField>
|
|
483
|
-
<UFormField label="Neuroticism">
|
|
484
|
-
<UInput
|
|
485
|
-
v-model.number="form.big_five_n"
|
|
486
|
-
type="number"
|
|
487
|
-
:min="0"
|
|
488
|
-
:max="100"
|
|
489
|
-
aria-label="Neuroticism score"
|
|
490
|
-
class="w-full"
|
|
491
|
-
/>
|
|
492
|
-
</UFormField>
|
|
493
|
-
</div>
|
|
494
|
-
</fieldset>
|
|
495
|
-
|
|
496
|
-
<!-- Knowledge & Communication -->
|
|
497
|
-
<fieldset>
|
|
498
|
-
<legend class="text-xs font-bold uppercase tracking-widest text-muted mb-4">Knowledge & Communication</legend>
|
|
499
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
500
|
-
<UFormField label="Mental Models" hint="Comma-separated">
|
|
501
|
-
<UInput
|
|
502
|
-
v-model="form.mental_models"
|
|
503
|
-
placeholder="e.g. Grand Slam Offer, Value Equation"
|
|
504
|
-
aria-label="Mental models, comma-separated"
|
|
505
|
-
class="w-full"
|
|
506
|
-
/>
|
|
507
|
-
</UFormField>
|
|
508
|
-
<UFormField label="Expertise Domains" hint="Comma-separated">
|
|
509
|
-
<UInput
|
|
510
|
-
v-model="form.expertise_domains"
|
|
511
|
-
placeholder="e.g. business strategy, offer creation"
|
|
512
|
-
aria-label="Expertise domains, comma-separated"
|
|
513
|
-
class="w-full"
|
|
514
|
-
/>
|
|
515
|
-
</UFormField>
|
|
516
|
-
<UFormField label="Frameworks" hint="Comma-separated">
|
|
517
|
-
<UInput
|
|
518
|
-
v-model="form.frameworks"
|
|
519
|
-
placeholder="e.g. $100M Offers, Value Equation"
|
|
520
|
-
aria-label="Frameworks, comma-separated"
|
|
521
|
-
class="w-full"
|
|
522
|
-
/>
|
|
523
|
-
</UFormField>
|
|
524
|
-
<UFormField label="Communication Tone">
|
|
525
|
-
<UInput
|
|
526
|
-
v-model="form.communication_tone"
|
|
527
|
-
placeholder="e.g. direct, high-energy"
|
|
528
|
-
aria-label="Communication tone"
|
|
529
|
-
class="w-full"
|
|
530
|
-
/>
|
|
531
|
-
</UFormField>
|
|
532
|
-
</div>
|
|
533
|
-
</fieldset>
|
|
534
|
-
|
|
535
|
-
<!-- Submit -->
|
|
536
|
-
<UButton
|
|
537
|
-
type="submit"
|
|
538
|
-
label="Create Persona"
|
|
539
|
-
icon="i-lucide-sparkles"
|
|
540
|
-
size="lg"
|
|
541
|
-
block
|
|
542
|
-
:loading="creating"
|
|
543
|
-
:disabled="!form.name.trim()"
|
|
544
|
-
/>
|
|
545
|
-
</form>
|
|
546
|
-
</UCard>
|
|
547
|
-
|
|
548
|
-
<!-- Empty state -->
|
|
549
|
-
<div v-if="!personas.length && !showForm" class="flex flex-col items-center justify-center gap-6 py-20">
|
|
550
|
-
<div class="rounded-full bg-muted/10 p-6">
|
|
551
|
-
<UIcon name="i-lucide-users" class="size-12 text-muted" />
|
|
552
|
-
</div>
|
|
553
|
-
<div class="text-center space-y-2">
|
|
554
|
-
<h3 class="text-base font-semibold">No personas yet</h3>
|
|
555
|
-
<p class="text-sm text-muted max-w-sm">
|
|
556
|
-
Personas define the behavioral DNA for your AI agents. Create one to get started.
|
|
557
|
-
</p>
|
|
558
|
-
</div>
|
|
559
|
-
<UButton
|
|
560
|
-
label="Create your first persona"
|
|
561
|
-
icon="i-lucide-plus"
|
|
562
|
-
size="lg"
|
|
563
|
-
@click="showForm = true"
|
|
564
|
-
/>
|
|
565
|
-
</div>
|
|
566
|
-
|
|
567
|
-
<!-- PR74 v2.92.0 — detail/edit drawer -->
|
|
568
|
-
<PersonaDetailDrawer
|
|
569
|
-
v-model="detailOpen"
|
|
570
|
-
:persona-id="detailPersonaId"
|
|
571
|
-
@saved="onDetailSaved"
|
|
572
|
-
@deleted="onDetailDeleted"
|
|
573
|
-
/>
|
|
574
|
-
|
|
575
|
-
<!-- Personas Grid -->
|
|
576
|
-
<div v-if="personas.length" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
577
|
-
<div
|
|
578
|
-
v-for="persona in personas"
|
|
579
|
-
:key="persona.id"
|
|
580
|
-
class="group flex flex-col rounded-2xl border border-default overflow-hidden cursor-pointer hover:border-primary/40 hover:shadow-lg transition-all"
|
|
581
|
-
role="button"
|
|
582
|
-
tabindex="0"
|
|
583
|
-
@click="openDetail(persona)"
|
|
584
|
-
@keydown.enter="openDetail(persona)"
|
|
585
|
-
>
|
|
586
|
-
<!-- Gradient header with avatar -->
|
|
587
|
-
<div
|
|
588
|
-
class="p-4 flex items-center gap-3"
|
|
589
|
-
:class="mbtiGradient(persona.mbti)"
|
|
590
|
-
>
|
|
591
|
-
<div class="shrink-0 size-12 rounded-xl bg-default/80 border border-default flex items-center justify-center shadow-sm backdrop-blur-sm">
|
|
592
|
-
<span class="text-sm font-bold tracking-tight text-highlighted">
|
|
593
|
-
{{ personaInitials(persona.name) }}
|
|
594
|
-
</span>
|
|
595
|
-
</div>
|
|
596
|
-
<div class="flex-1 min-w-0">
|
|
597
|
-
<h3 class="text-base font-bold truncate text-highlighted">{{ persona.name }}</h3>
|
|
598
|
-
<p v-if="persona.title" class="text-xs text-muted truncate mt-0.5">{{ persona.title }}</p>
|
|
599
|
-
</div>
|
|
600
|
-
<UBadge
|
|
601
|
-
v-if="personaAgentCount(persona.id) > 0"
|
|
602
|
-
:label="`${personaAgentCount(persona.id)} agents`"
|
|
603
|
-
variant="subtle"
|
|
604
|
-
color="primary"
|
|
605
|
-
size="xs"
|
|
606
|
-
class="shrink-0"
|
|
607
|
-
/>
|
|
608
|
-
</div>
|
|
609
|
-
|
|
610
|
-
<!-- Body -->
|
|
611
|
-
<div class="flex flex-col gap-3 flex-1 p-4">
|
|
612
|
-
<p v-if="persona.tagline" class="text-sm text-muted italic leading-relaxed line-clamp-2">
|
|
613
|
-
"{{ persona.tagline }}"
|
|
614
|
-
</p>
|
|
615
|
-
|
|
616
|
-
<!-- DNA Badges -->
|
|
617
|
-
<div class="flex flex-wrap gap-1.5">
|
|
618
|
-
<UBadge
|
|
619
|
-
v-if="persona.mbti"
|
|
620
|
-
:label="persona.mbti"
|
|
621
|
-
:color="mbtiColor(persona.mbti) as any"
|
|
622
|
-
variant="subtle"
|
|
623
|
-
size="xs"
|
|
624
|
-
/>
|
|
625
|
-
<UBadge
|
|
626
|
-
v-if="persona.disc?.primary"
|
|
627
|
-
:label="`DISC: ${persona.disc.primary}${persona.disc.secondary ? '/' + persona.disc.secondary : ''}`"
|
|
628
|
-
:color="discColor(persona.disc.primary) as any"
|
|
629
|
-
variant="subtle"
|
|
630
|
-
size="xs"
|
|
631
|
-
/>
|
|
632
|
-
<UBadge
|
|
633
|
-
v-if="persona.enneagram?.type"
|
|
634
|
-
:label="`E${persona.enneagram.type}${persona.enneagram.wing ? 'w' + persona.enneagram.wing : ''}`"
|
|
635
|
-
variant="outline"
|
|
636
|
-
size="xs"
|
|
637
|
-
/>
|
|
638
|
-
</div>
|
|
639
|
-
|
|
640
|
-
<!-- Expertise domains -->
|
|
641
|
-
<div v-if="persona.expertise_domains?.length" class="flex flex-wrap gap-1">
|
|
642
|
-
<UBadge
|
|
643
|
-
v-for="domain in persona.expertise_domains.slice(0, 3)"
|
|
644
|
-
:key="domain"
|
|
645
|
-
:label="domain"
|
|
646
|
-
variant="outline"
|
|
647
|
-
size="xs"
|
|
648
|
-
color="neutral"
|
|
649
|
-
/>
|
|
650
|
-
<UBadge
|
|
651
|
-
v-if="persona.expertise_domains.length > 3"
|
|
652
|
-
:label="`+${persona.expertise_domains.length - 3}`"
|
|
653
|
-
variant="outline"
|
|
654
|
-
size="xs"
|
|
655
|
-
color="neutral"
|
|
656
|
-
/>
|
|
657
|
-
</div>
|
|
658
|
-
|
|
659
|
-
<!-- Actions -->
|
|
660
|
-
<div class="pt-3 mt-auto border-t border-default space-y-3" @click.stop>
|
|
661
|
-
<div class="flex gap-2">
|
|
662
|
-
<UButton
|
|
663
|
-
label="Clone to Agent"
|
|
664
|
-
icon="i-lucide-copy"
|
|
665
|
-
size="sm"
|
|
666
|
-
variant="solid"
|
|
667
|
-
class="flex-1"
|
|
668
|
-
@click.stop="toggleClone(persona)"
|
|
669
|
-
/>
|
|
670
|
-
<UButton
|
|
671
|
-
icon="i-lucide-trash-2"
|
|
672
|
-
size="sm"
|
|
673
|
-
variant="ghost"
|
|
674
|
-
color="error"
|
|
675
|
-
:loading="deleting === persona.id"
|
|
676
|
-
aria-label="Delete persona"
|
|
677
|
-
@click.stop="deletePersona(persona)"
|
|
678
|
-
/>
|
|
679
|
-
</div>
|
|
680
|
-
|
|
681
|
-
<!-- Inline clone expansion -->
|
|
682
|
-
<div v-if="cloneExpandedId === persona.id" class="space-y-3 pt-2">
|
|
683
|
-
<UFormField label="Department" required>
|
|
684
|
-
<USelect
|
|
685
|
-
v-model="cloneDepartment"
|
|
686
|
-
:items="departmentOptions"
|
|
687
|
-
placeholder="Select department"
|
|
688
|
-
aria-label="Target department"
|
|
689
|
-
class="w-full"
|
|
690
|
-
/>
|
|
691
|
-
</UFormField>
|
|
692
|
-
<UFormField label="Tier" required>
|
|
693
|
-
<USelect
|
|
694
|
-
v-model="cloneTier"
|
|
695
|
-
:items="tierOptions"
|
|
696
|
-
placeholder="Select tier"
|
|
697
|
-
aria-label="Agent tier"
|
|
698
|
-
class="w-full"
|
|
699
|
-
/>
|
|
700
|
-
</UFormField>
|
|
701
|
-
<UButton
|
|
702
|
-
label="Confirm Clone"
|
|
703
|
-
icon="i-lucide-check"
|
|
704
|
-
size="sm"
|
|
705
|
-
block
|
|
706
|
-
:loading="cloning"
|
|
707
|
-
:disabled="!cloneDepartment || !cloneTier"
|
|
708
|
-
@click="cloneToAgent(persona)"
|
|
709
|
-
/>
|
|
710
|
-
</div>
|
|
711
|
-
</div>
|
|
712
|
-
</div>
|
|
713
|
-
</div>
|
|
714
|
-
</div>
|
|
715
|
-
</template>
|
|
716
|
-
</div>
|
|
717
|
-
</template>
|
|
718
|
-
</UDashboardPanel>
|
|
719
|
-
</template>
|