arkaos 2.72.0 → 2.73.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
|
-
2.
|
|
1
|
+
2.73.0
|
|
@@ -16,16 +16,27 @@ const ingestError = ref<string | null>(null)
|
|
|
16
16
|
const isDragging = ref(false)
|
|
17
17
|
const pasteText = ref('')
|
|
18
18
|
const pasteTitle = ref('')
|
|
19
|
+
// PR56 v2.73.0 — bulk URL ingest mode. Paste a list of URLs (one per
|
|
20
|
+
// line) and the backend queues one job per source.
|
|
21
|
+
const bulkUrls = ref('')
|
|
19
22
|
|
|
20
|
-
const activeInputMode = ref<'url' | 'file' | 'text' | 'research'>('url')
|
|
23
|
+
const activeInputMode = ref<'url' | 'file' | 'text' | 'research' | 'bulk'>('url')
|
|
21
24
|
|
|
22
25
|
const inputModes = [
|
|
23
26
|
{ label: 'URL', value: 'url' as const, icon: 'i-lucide-link' },
|
|
27
|
+
{ label: 'Bulk', value: 'bulk' as const, icon: 'i-lucide-list' },
|
|
24
28
|
{ label: 'File', value: 'file' as const, icon: 'i-lucide-upload' },
|
|
25
29
|
{ label: 'Text', value: 'text' as const, icon: 'i-lucide-type' },
|
|
26
30
|
{ label: 'Research', value: 'research' as const, icon: 'i-lucide-search' },
|
|
27
31
|
]
|
|
28
32
|
|
|
33
|
+
const bulkUrlCount = computed(() =>
|
|
34
|
+
bulkUrls.value
|
|
35
|
+
.split('\n')
|
|
36
|
+
.map((s) => s.trim())
|
|
37
|
+
.filter((s) => s.length > 0).length
|
|
38
|
+
)
|
|
39
|
+
|
|
29
40
|
function handleDrop(e: DragEvent) {
|
|
30
41
|
isDragging.value = false
|
|
31
42
|
const file = e.dataTransfer?.files?.[0]
|
|
@@ -87,6 +98,7 @@ function clearFile() {
|
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
const canIngest = computed(() => {
|
|
101
|
+
if (activeInputMode.value === 'bulk') return bulkUrlCount.value > 0
|
|
90
102
|
return detectedType.value !== null
|
|
91
103
|
})
|
|
92
104
|
|
|
@@ -178,7 +190,11 @@ onUnmounted(() => {
|
|
|
178
190
|
})
|
|
179
191
|
|
|
180
192
|
async function handleIngest() {
|
|
181
|
-
if (
|
|
193
|
+
if (
|
|
194
|
+
!detectedType.value
|
|
195
|
+
&& activeInputMode.value !== 'text'
|
|
196
|
+
&& activeInputMode.value !== 'bulk'
|
|
197
|
+
) return
|
|
182
198
|
|
|
183
199
|
ingestError.value = null
|
|
184
200
|
|
|
@@ -199,6 +215,17 @@ async function handleIngest() {
|
|
|
199
215
|
body: { source: pasteText.value.slice(0, 100), type: 'markdown', text: pasteText.value, title: pasteTitle.value },
|
|
200
216
|
})
|
|
201
217
|
}
|
|
218
|
+
// Bulk URL paste — one job per non-blank line, server caps at 50
|
|
219
|
+
else if (activeInputMode.value === 'bulk' && bulkUrlCount.value > 0) {
|
|
220
|
+
const sources = bulkUrls.value
|
|
221
|
+
.split('\n')
|
|
222
|
+
.map((s) => s.trim())
|
|
223
|
+
.filter((s) => s.length > 0)
|
|
224
|
+
await $fetch(`${apiBase}/api/knowledge/ingest-bulk`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
body: { sources },
|
|
227
|
+
})
|
|
228
|
+
}
|
|
202
229
|
// URL or Research — standard ingest
|
|
203
230
|
else {
|
|
204
231
|
const source = ingestUrl.value.trim()
|
|
@@ -215,6 +242,7 @@ async function handleIngest() {
|
|
|
215
242
|
clearFile()
|
|
216
243
|
pasteText.value = ''
|
|
217
244
|
pasteTitle.value = ''
|
|
245
|
+
bulkUrls.value = ''
|
|
218
246
|
|
|
219
247
|
// Refresh jobs table + connect WebSocket
|
|
220
248
|
fetchJobs()
|
|
@@ -415,6 +443,23 @@ function formatScore(score: number): string {
|
|
|
415
443
|
/>
|
|
416
444
|
</div>
|
|
417
445
|
|
|
446
|
+
<!-- Mode: Bulk URLs (PR56 v2.73.0) -->
|
|
447
|
+
<div v-if="activeInputMode === 'bulk'" class="space-y-3">
|
|
448
|
+
<UTextarea
|
|
449
|
+
v-model="bulkUrls"
|
|
450
|
+
placeholder="Paste one URL per line. Up to 50 sources per batch. https://www.youtube.com/watch?v=... https://example.com/article https://example.com/paper.pdf"
|
|
451
|
+
:rows="8"
|
|
452
|
+
size="lg"
|
|
453
|
+
class="w-full font-mono text-sm"
|
|
454
|
+
/>
|
|
455
|
+
<div class="flex items-center justify-between text-xs text-muted">
|
|
456
|
+
<span>{{ bulkUrlCount }} source{{ bulkUrlCount === 1 ? '' : 's' }} detected</span>
|
|
457
|
+
<span v-if="bulkUrlCount > 50" class="text-red-400">
|
|
458
|
+
Over the 50-source cap — extras will be rejected.
|
|
459
|
+
</span>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
418
463
|
<!-- Mode: Research -->
|
|
419
464
|
<div v-if="activeInputMode === 'research'" class="space-y-3">
|
|
420
465
|
<UInput
|
|
@@ -447,7 +492,11 @@ function formatScore(score: number): string {
|
|
|
447
492
|
</div>
|
|
448
493
|
|
|
449
494
|
<UButton
|
|
450
|
-
:label="
|
|
495
|
+
:label="
|
|
496
|
+
activeInputMode === 'research' ? 'Research & Index'
|
|
497
|
+
: activeInputMode === 'bulk' ? `Ingest ${bulkUrlCount} source${bulkUrlCount === 1 ? '' : 's'}`
|
|
498
|
+
: 'Ingest'
|
|
499
|
+
"
|
|
451
500
|
icon="i-lucide-zap"
|
|
452
501
|
size="md"
|
|
453
502
|
:disabled="!canIngest && !(activeInputMode === 'text' && pasteText.length > 50)"
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -487,6 +487,48 @@ def knowledge_ingest(body: dict):
|
|
|
487
487
|
return {"job_id": job.id, "source_type": source_type, "status": "queued"}
|
|
488
488
|
|
|
489
489
|
|
|
490
|
+
@app.post("/api/knowledge/ingest-bulk")
|
|
491
|
+
def knowledge_ingest_bulk(body: dict):
|
|
492
|
+
"""PR56 v2.73.0 — bulk URL ingest.
|
|
493
|
+
|
|
494
|
+
Accepts ``{"sources": ["url1", "url2", ...]}`` and queues one
|
|
495
|
+
background job per source. Returns ``{"jobs": [{...}, ...]}`` so the
|
|
496
|
+
dashboard can subscribe to each via the existing /ws/tasks stream.
|
|
497
|
+
Empty / whitespace-only lines are filtered. Duplicates collapse on
|
|
498
|
+
the JobManager side (one job per unique source).
|
|
499
|
+
"""
|
|
500
|
+
raw_sources = body.get("sources") or []
|
|
501
|
+
if not isinstance(raw_sources, list):
|
|
502
|
+
return {"error": "sources must be a list"}
|
|
503
|
+
cleaned = []
|
|
504
|
+
seen: set[str] = set()
|
|
505
|
+
for raw in raw_sources:
|
|
506
|
+
if not isinstance(raw, str):
|
|
507
|
+
continue
|
|
508
|
+
s = raw.strip()
|
|
509
|
+
if not s or s in seen:
|
|
510
|
+
continue
|
|
511
|
+
seen.add(s)
|
|
512
|
+
cleaned.append(s)
|
|
513
|
+
if not cleaned:
|
|
514
|
+
return {"error": "no valid sources provided"}
|
|
515
|
+
if len(cleaned) > 50:
|
|
516
|
+
return {"error": "bulk ingest is capped at 50 sources per request"}
|
|
517
|
+
jobs = []
|
|
518
|
+
for source in cleaned:
|
|
519
|
+
result = knowledge_ingest({"source": source})
|
|
520
|
+
if "error" in result:
|
|
521
|
+
jobs.append({"source": source, "error": result["error"]})
|
|
522
|
+
else:
|
|
523
|
+
jobs.append({
|
|
524
|
+
"source": source,
|
|
525
|
+
"job_id": result["job_id"],
|
|
526
|
+
"source_type": result.get("source_type"),
|
|
527
|
+
"status": result.get("status", "queued"),
|
|
528
|
+
})
|
|
529
|
+
return {"jobs": jobs, "count": len(jobs)}
|
|
530
|
+
|
|
531
|
+
|
|
490
532
|
@app.get("/api/tasks/{task_id}")
|
|
491
533
|
def task_detail(task_id: str):
|
|
492
534
|
"""Get a single task by ID. Also checks jobs."""
|