arkaos 3.24.0 → 3.25.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 CHANGED
@@ -1 +1 @@
1
- 3.24.0
1
+ 3.25.0
@@ -283,6 +283,19 @@ class VectorStore:
283
283
  "db_path": self._db_path,
284
284
  }
285
285
 
286
+ def list_sources(self) -> list[dict]:
287
+ """PR88c v3.25.0 — distinct sources with chunk counts.
288
+
289
+ Returns rows sorted by chunk count desc so the noisiest
290
+ sources surface first.
291
+ """
292
+ rows = self._db.execute(
293
+ "SELECT source, COUNT(*) AS chunks FROM chunks "
294
+ "WHERE source IS NOT NULL AND source != '' "
295
+ "GROUP BY source ORDER BY chunks DESC"
296
+ ).fetchall()
297
+ return [{"source": r["source"], "chunks": int(r["chunks"])} for r in rows]
298
+
286
299
  def clear(self) -> None:
287
300
  """Remove all data."""
288
301
  if self._vec_available:
@@ -0,0 +1,157 @@
1
+ <script setup lang="ts">
2
+ // PR88c v3.25.0 — Listing + management of indexed knowledge sources.
3
+ //
4
+ // Sits below the ingest UI on /knowledge. Loads GET /api/knowledge/sources
5
+ // (returns `{sources: [{source, chunks}], total}`), supports search,
6
+ // per-row Delete (DELETE /api/knowledge/sources?source=...). Pagination
7
+ // inline.
8
+
9
+ interface SourceRow {
10
+ source: string
11
+ chunks: number
12
+ }
13
+
14
+ const { fetchApi, apiBase } = useApi()
15
+ const toast = useToast()
16
+ const confirmDialog = useConfirmDialog()
17
+
18
+ const { data, status, error, refresh } = await fetchApi<{
19
+ sources: SourceRow[]
20
+ total: number
21
+ }>('/api/knowledge/sources')
22
+
23
+ const sources = computed(() => data.value?.sources ?? [])
24
+ const search = ref('')
25
+ const page = ref(1)
26
+ const pageSize = 15
27
+
28
+ const filtered = computed(() => {
29
+ const q = search.value.toLowerCase().trim()
30
+ if (!q) return sources.value
31
+ return sources.value.filter((s) => s.source.toLowerCase().includes(q))
32
+ })
33
+ const totalPages = computed(() => Math.max(1, Math.ceil(filtered.value.length / pageSize)))
34
+ const paged = computed(() =>
35
+ filtered.value.slice((page.value - 1) * pageSize, page.value * pageSize),
36
+ )
37
+
38
+ watch(search, () => { page.value = 1 })
39
+
40
+ async function remove(row: SourceRow) {
41
+ const ok = await confirmDialog({
42
+ title: 'Delete source?',
43
+ description: `Removes ${row.chunks} chunk${row.chunks === 1 ? '' : 's'} from the vector store. This cannot be undone.`,
44
+ confirmLabel: 'Delete',
45
+ cancelLabel: 'Cancel',
46
+ variant: 'danger',
47
+ })
48
+ if (!ok) return
49
+ try {
50
+ const res = await $fetch<{ deleted?: number, error?: string }>(
51
+ `${apiBase}/api/knowledge/sources`,
52
+ { method: 'DELETE', query: { source: row.source } },
53
+ )
54
+ if (res.error) throw new Error(res.error)
55
+ toast.add({
56
+ title: `Removed ${res.deleted ?? 0} chunks`,
57
+ description: row.source,
58
+ color: 'success',
59
+ })
60
+ await refresh()
61
+ } catch (err) {
62
+ toast.add({
63
+ title: 'Delete failed',
64
+ description: err instanceof Error ? err.message : 'unknown error',
65
+ color: 'error',
66
+ })
67
+ }
68
+ }
69
+
70
+ function sourceLabel(src: string): string {
71
+ if (src.startsWith('http')) {
72
+ try {
73
+ const u = new URL(src)
74
+ return u.hostname + u.pathname
75
+ } catch {
76
+ return src
77
+ }
78
+ }
79
+ return src
80
+ }
81
+ </script>
82
+
83
+ <template>
84
+ <UCard>
85
+ <template #header>
86
+ <div class="flex items-center justify-between gap-3">
87
+ <div>
88
+ <h3 class="text-lg font-bold">Indexed sources</h3>
89
+ <p class="text-xs text-muted mt-0.5">
90
+ Every distinct source contributing chunks to the vector store.
91
+ <span v-if="data?.total">{{ data.total }} total.</span>
92
+ </p>
93
+ </div>
94
+ <UButton
95
+ icon="i-lucide-refresh-cw"
96
+ variant="ghost"
97
+ size="sm"
98
+ aria-label="Refresh"
99
+ :loading="status === 'pending'"
100
+ @click="refresh"
101
+ />
102
+ </div>
103
+ </template>
104
+
105
+ <div v-if="error" class="py-6 text-center text-sm text-error">
106
+ Failed to load sources.
107
+ </div>
108
+ <div v-else-if="!sources.length" class="py-6 text-center text-sm text-muted">
109
+ <UIcon name="i-lucide-database" class="size-6 mx-auto mb-2" />
110
+ No sources indexed yet. Use the ingest panel above to add content.
111
+ </div>
112
+ <div v-else class="space-y-3">
113
+ <UInput
114
+ v-model="search"
115
+ icon="i-lucide-search"
116
+ placeholder="Filter by source URL or path…"
117
+ class="w-full"
118
+ />
119
+
120
+ <ul class="space-y-1.5">
121
+ <li
122
+ v-for="row in paged"
123
+ :key="row.source"
124
+ class="flex items-center gap-3 rounded-lg border border-default p-2.5 hover:border-primary/40 transition-colors"
125
+ >
126
+ <UIcon
127
+ :name="row.source.startsWith('http') ? 'i-lucide-link' : 'i-lucide-file-text'"
128
+ class="size-4 text-muted shrink-0"
129
+ />
130
+ <div class="flex-1 min-w-0">
131
+ <p class="text-sm font-mono truncate" :title="row.source">
132
+ {{ sourceLabel(row.source) }}
133
+ </p>
134
+ </div>
135
+ <UBadge :label="`${row.chunks} chunk${row.chunks === 1 ? '' : 's'}`" variant="subtle" size="xs" />
136
+ <UButton
137
+ icon="i-lucide-trash-2"
138
+ color="error"
139
+ variant="ghost"
140
+ size="xs"
141
+ aria-label="Delete source"
142
+ @click="remove(row)"
143
+ />
144
+ </li>
145
+ </ul>
146
+
147
+ <div v-if="totalPages > 1" class="flex items-center justify-center pt-2">
148
+ <UPagination
149
+ :page="page"
150
+ :total="filtered.length"
151
+ :items-per-page="pageSize"
152
+ @update:page="(val) => page = val"
153
+ />
154
+ </div>
155
+ </div>
156
+ </UCard>
157
+ </template>
@@ -906,6 +906,11 @@ function escapeRegex(value: string): string {
906
906
  </div>
907
907
  </template>
908
908
  </div>
909
+
910
+ <!-- PR88c v3.25.0 — Indexed sources management -->
911
+ <div class="mt-6">
912
+ <KnowledgeSourcesList />
913
+ </div>
909
914
  </template>
910
915
  </template>
911
916
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "3.24.0"
3
+ version = "3.25.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -839,6 +839,23 @@ def knowledge_search(q: str = Query(...), top_k: int = Query(5)):
839
839
  return {"results": results, "query": q, "total": len(results)}
840
840
 
841
841
 
842
+ @app.get("/api/knowledge/sources")
843
+ def knowledge_list_sources():
844
+ """PR88c v3.25.0 — list every distinct source + chunk count.
845
+
846
+ Returns ``{sources: [{source, chunks}], total: N}``. Sorted
847
+ descending by chunk count.
848
+ """
849
+ store = _get_vector_store()
850
+ if not store:
851
+ return {"sources": [], "total": 0, "error": "vector store unavailable"}
852
+ try:
853
+ rows = store.list_sources()
854
+ except Exception as exc: # noqa: BLE001
855
+ return {"sources": [], "total": 0, "error": str(exc)}
856
+ return {"sources": rows, "total": len(rows)}
857
+
858
+
842
859
  @app.delete("/api/knowledge/sources")
843
860
  def knowledge_delete_source(source: str = Query(...)):
844
861
  """PR71 v2.88.0 — remove all chunks from a given source.