arkaos 2.3.5 → 2.3.6

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.3.5
1
+ 2.3.6
@@ -178,25 +178,45 @@ onUnmounted(() => {
178
178
  })
179
179
 
180
180
  async function handleIngest() {
181
- if (!detectedType.value) return
181
+ if (!detectedType.value && activeInputMode.value !== 'text') return
182
182
 
183
183
  ingestError.value = null
184
- const source = ingestUrl.value.trim() || ingestFile.value?.name || ''
185
- const type = detectedType.value
186
-
187
- // Clear form immediately so user can submit more
188
- ingestUrl.value = ''
189
- clearFile()
190
- pasteText.value = ''
191
- pasteTitle.value = ''
192
184
 
193
185
  try {
194
- await $fetch<IngestResponse>(`${apiBase}/api/knowledge/ingest`, {
195
- method: 'POST',
196
- body: { source, type } satisfies IngestRequest,
197
- })
186
+ // File upload — use multipart form
187
+ if (activeInputMode.value === 'file' && ingestFile.value) {
188
+ const formData = new FormData()
189
+ formData.append('file', ingestFile.value)
190
+ await $fetch(`${apiBase}/api/knowledge/upload-file`, {
191
+ method: 'POST',
192
+ body: formData,
193
+ })
194
+ }
195
+ // Text paste — save to temp file via API
196
+ else if (activeInputMode.value === 'text' && pasteText.value.length > 10) {
197
+ await $fetch(`${apiBase}/api/knowledge/ingest`, {
198
+ method: 'POST',
199
+ body: { source: pasteText.value.slice(0, 100), type: 'markdown', text: pasteText.value, title: pasteTitle.value },
200
+ })
201
+ }
202
+ // URL or Research — standard ingest
203
+ else {
204
+ const source = ingestUrl.value.trim()
205
+ const type = detectedType.value
206
+ if (!source || !type) return
207
+ await $fetch(`${apiBase}/api/knowledge/ingest`, {
208
+ method: 'POST',
209
+ body: { source, type },
210
+ })
211
+ }
212
+
213
+ // Clear form immediately
214
+ ingestUrl.value = ''
215
+ clearFile()
216
+ pasteText.value = ''
217
+ pasteTitle.value = ''
198
218
 
199
- // Refresh jobs table + connect WebSocket for live updates
219
+ // Refresh jobs table + connect WebSocket
200
220
  fetchJobs()
201
221
  connectWebSocket()
202
222
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.3.5",
3
+ "version": "2.3.6",
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 = "2.3.5"
3
+ version = "2.3.6"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -345,6 +345,71 @@ def job_cancel(job_id: str):
345
345
  return {"error": "Can only cancel queued jobs"}
346
346
 
347
347
 
348
+ @app.post("/api/knowledge/upload")
349
+ async def knowledge_upload(file: Any = None):
350
+ """Upload a file for ingestion."""
351
+ from fastapi import UploadFile, File as FastAPIFile
352
+ # Re-import with proper type
353
+ pass
354
+
355
+
356
+ # Actual upload endpoint with proper signature
357
+ from fastapi import UploadFile, File as FastAPIFile
358
+
359
+ @app.post("/api/knowledge/upload-file")
360
+ async def knowledge_upload_file(file: UploadFile):
361
+ """Upload and ingest a file (PDF, audio, markdown)."""
362
+ import threading
363
+
364
+ media_dir = Path.home() / ".arkaos" / "media"
365
+ media_dir.mkdir(parents=True, exist_ok=True)
366
+
367
+ # Save uploaded file
368
+ file_path = media_dir / file.filename
369
+ content = await file.read()
370
+ file_path.write_bytes(content)
371
+
372
+ source = str(file_path)
373
+ from core.knowledge.ingest import detect_source_type
374
+ source_type = detect_source_type(source)
375
+
376
+ store = _get_vector_store()
377
+ if not store:
378
+ from core.knowledge.vector_store import VectorStore
379
+ kb_db = Path.home() / ".arkaos" / "knowledge.db"
380
+ kb_db.parent.mkdir(parents=True, exist_ok=True)
381
+ store = VectorStore(kb_db)
382
+
383
+ job_mgr = _get_job_manager()
384
+ job = job_mgr.create(source=source, source_type=source_type, title=file.filename)
385
+ job_id = job.id
386
+
387
+ def run_ingest():
388
+ from core.jobs.manager import JobManager as _JM
389
+ from core.knowledge.ingest import IngestEngine
390
+ local_mgr = _JM()
391
+ engine = IngestEngine(store)
392
+ def on_progress(pct, msg):
393
+ status = "embedding" if "embed" in msg.lower() or "index" in msg.lower() else "processing"
394
+ local_mgr.update_progress(job_id, pct, msg, status)
395
+ broadcast_from_thread({"type": "job_progress", "job_id": job_id, "progress": pct, "message": msg, "status": status})
396
+ try:
397
+ local_mgr.start(job_id)
398
+ result = engine.ingest(source, source_type, on_progress=on_progress)
399
+ if result.success:
400
+ local_mgr.complete(job_id, chunks_created=result.chunks_created)
401
+ broadcast_from_thread({"type": "job_complete", "job_id": job_id, "chunks_created": result.chunks_created})
402
+ else:
403
+ local_mgr.fail(job_id, result.error)
404
+ broadcast_from_thread({"type": "job_failed", "job_id": job_id, "error": result.error})
405
+ except Exception as e:
406
+ local_mgr.fail(job_id, str(e))
407
+ broadcast_from_thread({"type": "job_failed", "job_id": job_id, "error": str(e)})
408
+
409
+ threading.Thread(target=run_ingest, daemon=True).start()
410
+ return {"job_id": job_id, "source_type": source_type, "filename": file.filename, "status": "queued"}
411
+
412
+
348
413
  @app.post("/api/knowledge/ingest")
349
414
  def knowledge_ingest(body: dict):
350
415
  """Ingest content into the knowledge base. Runs in background with SQLite job tracking."""
@@ -352,6 +417,21 @@ def knowledge_ingest(body: dict):
352
417
 
353
418
  source = body.get("source", "")
354
419
  source_type = body.get("type", "")
420
+ text_content = body.get("text", "")
421
+ text_title = body.get("title", "")
422
+
423
+ # Handle direct text paste — save to temp markdown file
424
+ if text_content and len(text_content) > 10:
425
+ media_dir = Path.home() / ".arkaos" / "media"
426
+ media_dir.mkdir(parents=True, exist_ok=True)
427
+ safe_name = "".join(c if c.isalnum() or c in " -_" else "" for c in (text_title or source)[:40]).strip() or "pasted-text"
428
+ text_path = media_dir / f"{safe_name}.md"
429
+ # Add title as heading
430
+ md_content = f"# {text_title}\n\n{text_content}" if text_title else text_content
431
+ text_path.write_text(md_content, encoding="utf-8")
432
+ source = str(text_path)
433
+ source_type = "markdown"
434
+
355
435
  if not source:
356
436
  return {"error": "source is required"}
357
437