arkaos 3.65.0 → 3.66.1
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/core/__pycache__/favorites.cpython-313.pyc +0 -0
- package/dashboard/app/components/AgentDependencyGraph.vue +192 -0
- package/dashboard/app/pages/agents/[id].vue +8 -0
- package/dashboard/app/pages/agents/index.vue +6 -6
- package/dashboard/app/pages/personas.vue +719 -0
- package/departments/brand/agents/brand-director.yaml +40 -38
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.66.1
|
|
Binary file
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR98d v3.66.0 — Agent dependency graph.
|
|
3
|
+
//
|
|
4
|
+
// Shows the current agent in the middle, its linked personas above,
|
|
5
|
+
// and other agents that link to those same personas (siblings) below.
|
|
6
|
+
// Pure SVG, no graph lib. Frontend-only — reuses existing endpoints.
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
agentId: string
|
|
10
|
+
agentName: string
|
|
11
|
+
linkedPersonas: string[]
|
|
12
|
+
}
|
|
13
|
+
const props = defineProps<Props>()
|
|
14
|
+
|
|
15
|
+
const { fetchApi } = useApi()
|
|
16
|
+
|
|
17
|
+
// /api/personas/usage returns the reverse lookup we need.
|
|
18
|
+
const { data: usageData } = fetchApi<{
|
|
19
|
+
by_persona: Record<string, { agent_count: number, agent_ids: string[] }>
|
|
20
|
+
}>('/api/personas/usage')
|
|
21
|
+
|
|
22
|
+
// /api/personas for resolving persona names.
|
|
23
|
+
const { data: personasData } = fetchApi<{
|
|
24
|
+
personas: Array<{ id: string, name: string }>
|
|
25
|
+
}>('/api/personas')
|
|
26
|
+
|
|
27
|
+
function personaName(id: string): string {
|
|
28
|
+
return personasData.value?.personas?.find((p) => p.id === id)?.name ?? id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const siblings = computed<string[]>(() => {
|
|
32
|
+
const ids = new Set<string>()
|
|
33
|
+
const usage = usageData.value?.by_persona ?? {}
|
|
34
|
+
for (const pid of props.linkedPersonas) {
|
|
35
|
+
for (const aid of (usage[pid]?.agent_ids ?? [])) {
|
|
36
|
+
if (aid && aid !== props.agentId) ids.add(aid)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Array.from(ids).slice(0, 6)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Layout constants — keep tight so the graph fits the hero card.
|
|
43
|
+
const WIDTH = 700
|
|
44
|
+
const HEIGHT = 220
|
|
45
|
+
const CENTER_X = WIDTH / 2
|
|
46
|
+
const CENTER_Y = HEIGHT / 2
|
|
47
|
+
const NODE_W = 110
|
|
48
|
+
const NODE_H = 32
|
|
49
|
+
const NODE_RX = 8
|
|
50
|
+
|
|
51
|
+
function distributeX(count: number, idx: number): number {
|
|
52
|
+
if (count === 1) return CENTER_X
|
|
53
|
+
const span = Math.min(WIDTH - 80, count * (NODE_W + 20))
|
|
54
|
+
const start = (WIDTH - span) / 2 + NODE_W / 2
|
|
55
|
+
const step = count > 1 ? (span - NODE_W) / (count - 1) : 0
|
|
56
|
+
return start + idx * step
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const personaPositions = computed(() =>
|
|
60
|
+
props.linkedPersonas.map((id, i) => ({
|
|
61
|
+
id,
|
|
62
|
+
x: distributeX(props.linkedPersonas.length, i),
|
|
63
|
+
y: 32,
|
|
64
|
+
})),
|
|
65
|
+
)
|
|
66
|
+
const siblingPositions = computed(() =>
|
|
67
|
+
siblings.value.map((id, i) => ({
|
|
68
|
+
id,
|
|
69
|
+
x: distributeX(siblings.value.length, i),
|
|
70
|
+
y: HEIGHT - 32,
|
|
71
|
+
})),
|
|
72
|
+
)
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div
|
|
77
|
+
v-if="props.linkedPersonas.length > 0 || siblings.length > 0"
|
|
78
|
+
class="rounded-xl border border-default bg-elevated/10 p-5"
|
|
79
|
+
>
|
|
80
|
+
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted mb-3">
|
|
81
|
+
Dependency graph
|
|
82
|
+
</h3>
|
|
83
|
+
<svg
|
|
84
|
+
:viewBox="`0 0 ${WIDTH} ${HEIGHT}`"
|
|
85
|
+
class="w-full"
|
|
86
|
+
preserveAspectRatio="xMidYMid meet"
|
|
87
|
+
>
|
|
88
|
+
<!-- Connector lines (personas → center) -->
|
|
89
|
+
<line
|
|
90
|
+
v-for="p in personaPositions"
|
|
91
|
+
:key="`pl-${p.id}`"
|
|
92
|
+
:x1="p.x"
|
|
93
|
+
:y1="p.y + NODE_H / 2"
|
|
94
|
+
:x2="CENTER_X"
|
|
95
|
+
:y2="CENTER_Y - NODE_H / 2"
|
|
96
|
+
class="stroke-default"
|
|
97
|
+
stroke-width="1"
|
|
98
|
+
/>
|
|
99
|
+
<!-- Connector lines (center → siblings) -->
|
|
100
|
+
<line
|
|
101
|
+
v-for="s in siblingPositions"
|
|
102
|
+
:key="`sl-${s.id}`"
|
|
103
|
+
:x1="CENTER_X"
|
|
104
|
+
:y1="CENTER_Y + NODE_H / 2"
|
|
105
|
+
:x2="s.x"
|
|
106
|
+
:y2="s.y - NODE_H / 2"
|
|
107
|
+
class="stroke-default"
|
|
108
|
+
stroke-width="1"
|
|
109
|
+
stroke-dasharray="3,2"
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
<!-- Persona nodes (top) -->
|
|
113
|
+
<g
|
|
114
|
+
v-for="p in personaPositions"
|
|
115
|
+
:key="`p-${p.id}`"
|
|
116
|
+
>
|
|
117
|
+
<a :href="`/personas/${p.id}`">
|
|
118
|
+
<rect
|
|
119
|
+
:x="p.x - NODE_W / 2"
|
|
120
|
+
:y="p.y - NODE_H / 2"
|
|
121
|
+
:width="NODE_W"
|
|
122
|
+
:height="NODE_H"
|
|
123
|
+
:rx="NODE_RX"
|
|
124
|
+
class="fill-emerald-500/10 stroke-emerald-500/40"
|
|
125
|
+
stroke-width="1"
|
|
126
|
+
/>
|
|
127
|
+
<text
|
|
128
|
+
:x="p.x"
|
|
129
|
+
:y="p.y + 4"
|
|
130
|
+
text-anchor="middle"
|
|
131
|
+
class="fill-emerald-700 dark:fill-emerald-300 text-xs"
|
|
132
|
+
>
|
|
133
|
+
{{ personaName(p.id).slice(0, 14) }}
|
|
134
|
+
</text>
|
|
135
|
+
<title>{{ personaName(p.id) }}</title>
|
|
136
|
+
</a>
|
|
137
|
+
</g>
|
|
138
|
+
|
|
139
|
+
<!-- Centre node: current agent -->
|
|
140
|
+
<g>
|
|
141
|
+
<rect
|
|
142
|
+
:x="CENTER_X - NODE_W / 2 - 10"
|
|
143
|
+
:y="CENTER_Y - NODE_H / 2"
|
|
144
|
+
:width="NODE_W + 20"
|
|
145
|
+
:height="NODE_H"
|
|
146
|
+
:rx="NODE_RX"
|
|
147
|
+
class="fill-primary/15 stroke-primary"
|
|
148
|
+
stroke-width="1.5"
|
|
149
|
+
/>
|
|
150
|
+
<text
|
|
151
|
+
:x="CENTER_X"
|
|
152
|
+
:y="CENTER_Y + 4"
|
|
153
|
+
text-anchor="middle"
|
|
154
|
+
class="fill-primary text-sm font-semibold"
|
|
155
|
+
>
|
|
156
|
+
{{ props.agentName.slice(0, 16) }}
|
|
157
|
+
</text>
|
|
158
|
+
</g>
|
|
159
|
+
|
|
160
|
+
<!-- Sibling agent nodes (bottom) -->
|
|
161
|
+
<g
|
|
162
|
+
v-for="s in siblingPositions"
|
|
163
|
+
:key="`s-${s.id}`"
|
|
164
|
+
>
|
|
165
|
+
<a :href="`/agents/${s.id}`">
|
|
166
|
+
<rect
|
|
167
|
+
:x="s.x - NODE_W / 2"
|
|
168
|
+
:y="s.y - NODE_H / 2"
|
|
169
|
+
:width="NODE_W"
|
|
170
|
+
:height="NODE_H"
|
|
171
|
+
:rx="NODE_RX"
|
|
172
|
+
class="fill-blue-500/10 stroke-blue-500/40"
|
|
173
|
+
stroke-width="1"
|
|
174
|
+
/>
|
|
175
|
+
<text
|
|
176
|
+
:x="s.x"
|
|
177
|
+
:y="s.y + 4"
|
|
178
|
+
text-anchor="middle"
|
|
179
|
+
class="fill-blue-700 dark:fill-blue-300 text-xs"
|
|
180
|
+
>
|
|
181
|
+
{{ s.id.slice(0, 14) }}
|
|
182
|
+
</text>
|
|
183
|
+
<title>{{ s.id }}</title>
|
|
184
|
+
</a>
|
|
185
|
+
</g>
|
|
186
|
+
</svg>
|
|
187
|
+
<p class="text-xs text-muted mt-2 italic">
|
|
188
|
+
Top: linked personas · Centre: this agent · Bottom: siblings (other
|
|
189
|
+
agents linking the same personas)
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
</template>
|
|
@@ -658,6 +658,14 @@ function formatTokens(n: number): string {
|
|
|
658
658
|
</div>
|
|
659
659
|
</section>
|
|
660
660
|
|
|
661
|
+
<!-- ===== DEPENDENCY GRAPH (PR98d) ===== -->
|
|
662
|
+
<AgentDependencyGraph
|
|
663
|
+
v-if="agent.linked_personas && agent.linked_personas.length > 0"
|
|
664
|
+
:agent-id="agent.id"
|
|
665
|
+
:agent-name="agent.name"
|
|
666
|
+
:linked-personas="agent.linked_personas"
|
|
667
|
+
/>
|
|
668
|
+
|
|
661
669
|
<!-- ===== BIO (PR86d) ===== -->
|
|
662
670
|
<section
|
|
663
671
|
v-if="(agent as any).bio_md"
|
|
@@ -79,6 +79,12 @@ const tierFilter = ref(String(route.query.tier ?? 'all'))
|
|
|
79
79
|
const page = ref(1)
|
|
80
80
|
const pageSize = 15
|
|
81
81
|
|
|
82
|
+
// PR86a + PR92b — favorites refs must exist BEFORE the filter watcher
|
|
83
|
+
// below references them (otherwise TDZ blows up the whole page).
|
|
84
|
+
const favs = useFavorites()
|
|
85
|
+
await favs.load()
|
|
86
|
+
const favoritesOnly = ref(route.query.fav === '1')
|
|
87
|
+
|
|
82
88
|
const departments = computed(() => {
|
|
83
89
|
const depts = new Set(agents.value.map(a => a.department))
|
|
84
90
|
return [
|
|
@@ -262,12 +268,6 @@ defineShortcuts({
|
|
|
262
268
|
enter: () => cursorOpen(),
|
|
263
269
|
})
|
|
264
270
|
|
|
265
|
-
// PR86a v3.15.0 — favorites.
|
|
266
|
-
// PR92b v3.40.0 — favoritesOnly persists in URL (`?fav=1`).
|
|
267
|
-
const favs = useFavorites()
|
|
268
|
-
await favs.load()
|
|
269
|
-
const favoritesOnly = ref(route.query.fav === '1')
|
|
270
|
-
|
|
271
271
|
// PR83b v3.4.0 — bulk selection + delete.
|
|
272
272
|
// PR84b v3.8.0 — bulk move department.
|
|
273
273
|
const confirmDialog = useConfirmDialog()
|
|
@@ -0,0 +1,719 @@
|
|
|
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>
|
|
@@ -4,18 +4,19 @@ role: Creative Director
|
|
|
4
4
|
department: brand
|
|
5
5
|
tier: 1
|
|
6
6
|
model: sonnet
|
|
7
|
+
|
|
7
8
|
behavioral_dna:
|
|
8
9
|
disc:
|
|
9
10
|
primary: S
|
|
10
11
|
secondary: I
|
|
11
|
-
communication_style: Thoughtful, visual, builds consensus before executing
|
|
12
|
-
under_pressure: Protects creative quality, refuses to rush aesthetics
|
|
13
|
-
motivator: Beautiful, meaningful brands that resonate emotionally
|
|
12
|
+
communication_style: "Thoughtful, visual, builds consensus before executing"
|
|
13
|
+
under_pressure: "Protects creative quality, refuses to rush aesthetics"
|
|
14
|
+
motivator: "Beautiful, meaningful brands that resonate emotionally"
|
|
14
15
|
enneagram:
|
|
15
16
|
type: 4
|
|
16
17
|
wing: 3
|
|
17
|
-
core_motivation: Creating unique, authentic brand identities
|
|
18
|
-
core_fear: Producing generic, forgettable brand work
|
|
18
|
+
core_motivation: "Creating unique, authentic brand identities"
|
|
19
|
+
core_fear: "Producing generic, forgettable brand work"
|
|
19
20
|
subtype: social
|
|
20
21
|
big_five:
|
|
21
22
|
openness: 92
|
|
@@ -25,52 +26,53 @@ behavioral_dna:
|
|
|
25
26
|
neuroticism: 35
|
|
26
27
|
mbti:
|
|
27
28
|
type: INFP
|
|
29
|
+
|
|
28
30
|
mental_models:
|
|
29
31
|
primary:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
- "Primal Branding 7 Elements (Hanlon)"
|
|
33
|
+
- "Brand Identity Process (Wheeler)"
|
|
34
|
+
- "12 Archetypes (Jung)"
|
|
33
35
|
secondary:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
- "StoryBrand SB7 (Miller)"
|
|
37
|
+
- "Positioning (Ries/Trout)"
|
|
38
|
+
- "Design Thinking (IDEO)"
|
|
39
|
+
|
|
37
40
|
authority:
|
|
38
41
|
orchestrate: true
|
|
39
42
|
approve_quality: true
|
|
40
43
|
delegates_to:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
- visual-designer
|
|
45
|
+
- ux-designer
|
|
46
|
+
- brand-copywriter
|
|
47
|
+
- brand-strategist
|
|
45
48
|
escalates_to: coo-sofia
|
|
49
|
+
|
|
46
50
|
expertise:
|
|
47
51
|
domains:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
- brand identity creation
|
|
53
|
+
- visual design direction
|
|
54
|
+
- UX/UI strategy
|
|
55
|
+
- design systems
|
|
56
|
+
- brand voice & tone
|
|
57
|
+
- creative direction
|
|
54
58
|
frameworks:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
- Primal Branding (Hanlon)
|
|
60
|
+
- StoryBrand (Miller)
|
|
61
|
+
- Brand Archetypes (Jung)
|
|
62
|
+
- Wheeler Process
|
|
63
|
+
- Atomic Design (Frost)
|
|
64
|
+
- Nielsen Heuristics
|
|
65
|
+
- Dieter Rams 10 Principles
|
|
66
|
+
- Double Diamond
|
|
63
67
|
depth: master
|
|
64
|
-
years_equivalent:
|
|
68
|
+
years_equivalent: 14
|
|
69
|
+
|
|
65
70
|
communication:
|
|
66
71
|
language: en
|
|
67
|
-
tone: warm, visual, metaphor-rich
|
|
72
|
+
tone: "warm, visual, metaphor-rich"
|
|
68
73
|
vocabulary_level: advanced
|
|
69
|
-
preferred_format: mood boards, visual references, brand decks
|
|
74
|
+
preferred_format: "mood boards, visual references, brand decks"
|
|
70
75
|
avoid:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
frameworks: []
|
|
75
|
-
expertise_domains: []
|
|
76
|
-
linked_personas: []
|
|
76
|
+
- "design by committee"
|
|
77
|
+
- "trendy without substance"
|
|
78
|
+
- "skipping strategy for visuals"
|
package/package.json
CHANGED