arkaos 3.13.0 → 3.14.0
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__/trash.cpython-313.pyc +0 -0
- package/dashboard/app/components/GlobalSearch.vue +154 -0
- package/dashboard/app/components/KeyboardShortcutsHelp.vue +1 -0
- package/dashboard/app/composables/useDashboard.ts +4 -1
- package/dashboard/app/layouts/default.vue +1 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +93 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.14.0
|
|
Binary file
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR85d v3.14.0 — global search command palette.
|
|
3
|
+
//
|
|
4
|
+
// Opens via `/` shortcut. Debounced fetch against /api/search hits
|
|
5
|
+
// agents, personas, departments, commands. Navigate by Enter or click.
|
|
6
|
+
|
|
7
|
+
const { searchOpen } = useDashboard()
|
|
8
|
+
const { apiBase } = useApi()
|
|
9
|
+
const router = useRouter()
|
|
10
|
+
|
|
11
|
+
interface SearchResult {
|
|
12
|
+
kind: 'agent' | 'persona' | 'department' | 'command'
|
|
13
|
+
id: string
|
|
14
|
+
label: string
|
|
15
|
+
sublabel: string
|
|
16
|
+
to: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const query = ref('')
|
|
20
|
+
const results = ref<SearchResult[]>([])
|
|
21
|
+
const loading = ref(false)
|
|
22
|
+
let abortCtl: AbortController | null = null
|
|
23
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
24
|
+
|
|
25
|
+
watch(query, (q) => {
|
|
26
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
27
|
+
if (abortCtl) abortCtl.abort()
|
|
28
|
+
if (!q.trim()) {
|
|
29
|
+
results.value = []
|
|
30
|
+
loading.value = false
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
debounceTimer = setTimeout(async () => {
|
|
34
|
+
loading.value = true
|
|
35
|
+
abortCtl = new AbortController()
|
|
36
|
+
try {
|
|
37
|
+
const res = await $fetch<{ results: SearchResult[] }>(
|
|
38
|
+
`${apiBase}/api/search`,
|
|
39
|
+
{ query: { q, limit: 20 }, signal: abortCtl.signal },
|
|
40
|
+
)
|
|
41
|
+
results.value = res.results ?? []
|
|
42
|
+
} catch {
|
|
43
|
+
results.value = []
|
|
44
|
+
} finally {
|
|
45
|
+
loading.value = false
|
|
46
|
+
}
|
|
47
|
+
}, 180)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
watch(searchOpen, (open) => {
|
|
51
|
+
if (!open) {
|
|
52
|
+
query.value = ''
|
|
53
|
+
results.value = []
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function pickResult(r: SearchResult) {
|
|
58
|
+
searchOpen.value = false
|
|
59
|
+
router.push(r.to)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const kindMeta: Record<SearchResult['kind'], { icon: string, color: string }> = {
|
|
63
|
+
agent: { icon: 'i-lucide-users', color: 'text-primary' },
|
|
64
|
+
persona: { icon: 'i-lucide-user-plus', color: 'text-emerald-500' },
|
|
65
|
+
department: { icon: 'i-lucide-folder-tree', color: 'text-amber-500' },
|
|
66
|
+
command: { icon: 'i-lucide-terminal', color: 'text-blue-500' },
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<UModal
|
|
72
|
+
v-model:open="searchOpen"
|
|
73
|
+
title="Search"
|
|
74
|
+
:ui="{ content: 'max-w-2xl' }"
|
|
75
|
+
>
|
|
76
|
+
<template #content>
|
|
77
|
+
<UCard :ui="{ body: 'p-0' }">
|
|
78
|
+
<template #header>
|
|
79
|
+
<div class="flex items-center gap-3">
|
|
80
|
+
<UIcon name="i-lucide-search" class="size-5 text-muted shrink-0" />
|
|
81
|
+
<UInput
|
|
82
|
+
v-model="query"
|
|
83
|
+
placeholder="Search agents, personas, departments, commands…"
|
|
84
|
+
size="lg"
|
|
85
|
+
autofocus
|
|
86
|
+
:ui="{ root: 'flex-1', base: 'border-0 shadow-none ring-0 focus:ring-0 px-0' }"
|
|
87
|
+
/>
|
|
88
|
+
<kbd class="px-1.5 py-0.5 rounded bg-elevated/50 text-xs font-mono text-muted shrink-0">
|
|
89
|
+
esc
|
|
90
|
+
</kbd>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<div class="max-h-[60vh] overflow-y-auto">
|
|
95
|
+
<div v-if="loading" class="p-6 text-center text-sm text-muted">
|
|
96
|
+
<UIcon name="i-lucide-loader-2" class="size-4 animate-spin inline" />
|
|
97
|
+
Searching…
|
|
98
|
+
</div>
|
|
99
|
+
<div
|
|
100
|
+
v-else-if="!query.trim()"
|
|
101
|
+
class="p-6 text-center text-sm text-muted"
|
|
102
|
+
>
|
|
103
|
+
Start typing to search across the whole workspace.
|
|
104
|
+
</div>
|
|
105
|
+
<div
|
|
106
|
+
v-else-if="results.length === 0"
|
|
107
|
+
class="p-6 text-center text-sm text-muted"
|
|
108
|
+
>
|
|
109
|
+
No matches for <span class="font-mono">{{ query }}</span>.
|
|
110
|
+
</div>
|
|
111
|
+
<ul v-else class="divide-y divide-default">
|
|
112
|
+
<li
|
|
113
|
+
v-for="r in results"
|
|
114
|
+
:key="`${r.kind}:${r.id}`"
|
|
115
|
+
class="px-4 py-2.5 hover:bg-elevated/40 cursor-pointer transition-colors"
|
|
116
|
+
@click="pickResult(r)"
|
|
117
|
+
>
|
|
118
|
+
<div class="flex items-center gap-3">
|
|
119
|
+
<UIcon
|
|
120
|
+
:name="kindMeta[r.kind].icon"
|
|
121
|
+
class="size-4 shrink-0"
|
|
122
|
+
:class="kindMeta[r.kind].color"
|
|
123
|
+
/>
|
|
124
|
+
<div class="min-w-0 flex-1">
|
|
125
|
+
<p class="text-sm font-semibold truncate">{{ r.label }}</p>
|
|
126
|
+
<p class="text-xs text-muted truncate">{{ r.sublabel }}</p>
|
|
127
|
+
</div>
|
|
128
|
+
<UBadge
|
|
129
|
+
:label="r.kind"
|
|
130
|
+
variant="subtle"
|
|
131
|
+
size="xs"
|
|
132
|
+
class="capitalize shrink-0"
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</li>
|
|
136
|
+
</ul>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<template #footer>
|
|
140
|
+
<div class="text-xs text-muted flex items-center gap-3">
|
|
141
|
+
<span>
|
|
142
|
+
<kbd class="px-1.5 py-0.5 rounded bg-elevated/50 font-mono">/</kbd>
|
|
143
|
+
opens this
|
|
144
|
+
</span>
|
|
145
|
+
<span>
|
|
146
|
+
<kbd class="px-1.5 py-0.5 rounded bg-elevated/50 font-mono">esc</kbd>
|
|
147
|
+
closes
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
</UCard>
|
|
152
|
+
</template>
|
|
153
|
+
</UModal>
|
|
154
|
+
</template>
|
|
@@ -5,6 +5,8 @@ const _useDashboard = () => {
|
|
|
5
5
|
const router = useRouter()
|
|
6
6
|
const route = useRoute()
|
|
7
7
|
const shortcutsHelpOpen = useState('shortcutsHelpOpen', () => false)
|
|
8
|
+
// PR85d v3.14.0 — global search palette state.
|
|
9
|
+
const searchOpen = useState('searchOpen', () => false)
|
|
8
10
|
|
|
9
11
|
function contextualNew() {
|
|
10
12
|
const path = route.path
|
|
@@ -27,9 +29,10 @@ const _useDashboard = () => {
|
|
|
27
29
|
'g-r': () => router.push('/trash'),
|
|
28
30
|
n: () => contextualNew(),
|
|
29
31
|
'?': () => { shortcutsHelpOpen.value = !shortcutsHelpOpen.value },
|
|
32
|
+
'/': () => { searchOpen.value = true },
|
|
30
33
|
})
|
|
31
34
|
|
|
32
|
-
return { shortcutsHelpOpen }
|
|
35
|
+
return { shortcutsHelpOpen, searchOpen }
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export const useDashboard = createSharedComposable(_useDashboard)
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -1331,6 +1331,99 @@ def persona_delete(persona_id: str):
|
|
|
1331
1331
|
return {"error": "Persona not found"}
|
|
1332
1332
|
|
|
1333
1333
|
|
|
1334
|
+
# --- Global search (PR85d v3.14.0) ---
|
|
1335
|
+
|
|
1336
|
+
@app.get("/api/search")
|
|
1337
|
+
def global_search(q: str = "", limit: int = 20):
|
|
1338
|
+
"""Fuzzy substring search across agents, personas, departments, commands.
|
|
1339
|
+
|
|
1340
|
+
Returns a flat list of result objects with a shape the dashboard
|
|
1341
|
+
UCommandPalette can render directly:
|
|
1342
|
+
{results: [{kind, id, label, sublabel, to}]}
|
|
1343
|
+
"""
|
|
1344
|
+
query = (q or "").strip().lower()
|
|
1345
|
+
if not query:
|
|
1346
|
+
return {"results": []}
|
|
1347
|
+
results: list[dict] = []
|
|
1348
|
+
|
|
1349
|
+
# Agents
|
|
1350
|
+
for a in _load_agents():
|
|
1351
|
+
haystack = " ".join(filter(None, [
|
|
1352
|
+
str(a.get("name") or ""),
|
|
1353
|
+
str(a.get("role") or ""),
|
|
1354
|
+
str(a.get("department") or ""),
|
|
1355
|
+
str(a.get("id") or ""),
|
|
1356
|
+
])).lower()
|
|
1357
|
+
if query in haystack:
|
|
1358
|
+
results.append({
|
|
1359
|
+
"kind": "agent",
|
|
1360
|
+
"id": a.get("id"),
|
|
1361
|
+
"label": a.get("name") or a.get("id"),
|
|
1362
|
+
"sublabel": f"{a.get('role') or '—'} · {a.get('department') or '—'}",
|
|
1363
|
+
"to": f"/agents/{a.get('id')}",
|
|
1364
|
+
})
|
|
1365
|
+
|
|
1366
|
+
# Personas
|
|
1367
|
+
mgr = _get_persona_manager()
|
|
1368
|
+
if mgr:
|
|
1369
|
+
try:
|
|
1370
|
+
for p in (mgr.list() or []):
|
|
1371
|
+
haystack = " ".join(filter(None, [
|
|
1372
|
+
str(p.get("name") or ""),
|
|
1373
|
+
str(p.get("title") or ""),
|
|
1374
|
+
str(p.get("source") or ""),
|
|
1375
|
+
str(p.get("mbti") or ""),
|
|
1376
|
+
str(p.get("id") or ""),
|
|
1377
|
+
])).lower()
|
|
1378
|
+
if query in haystack:
|
|
1379
|
+
results.append({
|
|
1380
|
+
"kind": "persona",
|
|
1381
|
+
"id": p.get("id"),
|
|
1382
|
+
"label": p.get("name") or p.get("id"),
|
|
1383
|
+
"sublabel": f"{p.get('title') or '—'} · {p.get('mbti') or '—'}",
|
|
1384
|
+
"to": f"/personas/{p.get('id')}",
|
|
1385
|
+
})
|
|
1386
|
+
except Exception:
|
|
1387
|
+
pass
|
|
1388
|
+
|
|
1389
|
+
# Departments — derive distinct list from agents
|
|
1390
|
+
seen_depts: set[str] = set()
|
|
1391
|
+
for a in _load_agents():
|
|
1392
|
+
d = a.get("department")
|
|
1393
|
+
if d:
|
|
1394
|
+
seen_depts.add(d)
|
|
1395
|
+
for d in sorted(seen_depts):
|
|
1396
|
+
if query in d.lower():
|
|
1397
|
+
results.append({
|
|
1398
|
+
"kind": "department",
|
|
1399
|
+
"id": d,
|
|
1400
|
+
"label": d.capitalize(),
|
|
1401
|
+
"sublabel": "Department",
|
|
1402
|
+
"to": f"/agents?department={d}",
|
|
1403
|
+
})
|
|
1404
|
+
|
|
1405
|
+
# Commands
|
|
1406
|
+
try:
|
|
1407
|
+
for c in _load_commands():
|
|
1408
|
+
haystack = " ".join(filter(None, [
|
|
1409
|
+
str(c.get("command") or ""),
|
|
1410
|
+
str(c.get("description") or ""),
|
|
1411
|
+
str(c.get("department") or ""),
|
|
1412
|
+
])).lower()
|
|
1413
|
+
if query in haystack:
|
|
1414
|
+
results.append({
|
|
1415
|
+
"kind": "command",
|
|
1416
|
+
"id": c.get("command"),
|
|
1417
|
+
"label": c.get("command"),
|
|
1418
|
+
"sublabel": c.get("description") or c.get("department") or "",
|
|
1419
|
+
"to": "/commands",
|
|
1420
|
+
})
|
|
1421
|
+
except Exception:
|
|
1422
|
+
pass
|
|
1423
|
+
|
|
1424
|
+
return {"results": results[: max(0, int(limit))]}
|
|
1425
|
+
|
|
1426
|
+
|
|
1334
1427
|
# --- Trash / Undo (PR85b v3.12.0) ---
|
|
1335
1428
|
|
|
1336
1429
|
@app.get("/api/trash")
|