arkaos 3.35.0 → 3.36.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.
|
|
1
|
+
3.36.0
|
|
@@ -143,13 +143,55 @@ await favs.load()
|
|
|
143
143
|
const favoritesOnly = ref(false)
|
|
144
144
|
|
|
145
145
|
// PR87b v3.20.0 — import .md persona files.
|
|
146
|
+
// PR91b v3.36.0 — extended with URL import.
|
|
146
147
|
const importInput = ref<HTMLInputElement | null>(null)
|
|
147
148
|
const importing = ref(false)
|
|
149
|
+
const urlImportOpen = ref(false)
|
|
150
|
+
const urlImportText = ref('')
|
|
148
151
|
|
|
149
152
|
function triggerImport() {
|
|
150
153
|
importInput.value?.click()
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
async function runUrlImport() {
|
|
157
|
+
const urls = urlImportText.value
|
|
158
|
+
.split('\n')
|
|
159
|
+
.map((s) => s.trim())
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
if (urls.length === 0) return
|
|
162
|
+
importing.value = true
|
|
163
|
+
try {
|
|
164
|
+
const res = await $fetch<{
|
|
165
|
+
imported: number
|
|
166
|
+
failed: number
|
|
167
|
+
results: Array<{ filename: string, status: string, id?: string, error?: string }>
|
|
168
|
+
error?: string
|
|
169
|
+
}>(`${apiBase}/api/personas/import`, { method: 'POST', body: { urls } })
|
|
170
|
+
if (res.error) throw new Error(res.error)
|
|
171
|
+
toast.add({
|
|
172
|
+
title: res.imported > 0
|
|
173
|
+
? `Imported ${res.imported} persona${res.imported === 1 ? '' : 's'}`
|
|
174
|
+
: 'Nothing imported',
|
|
175
|
+
description: res.failed > 0 ? `${res.failed} failed` : undefined,
|
|
176
|
+
color: res.imported > 0 && res.failed === 0
|
|
177
|
+
? 'success'
|
|
178
|
+
: res.imported > 0 ? 'warning' : 'error',
|
|
179
|
+
icon: 'i-lucide-globe',
|
|
180
|
+
})
|
|
181
|
+
await refreshAll()
|
|
182
|
+
urlImportText.value = ''
|
|
183
|
+
urlImportOpen.value = false
|
|
184
|
+
} catch (err) {
|
|
185
|
+
toast.add({
|
|
186
|
+
title: 'URL import failed',
|
|
187
|
+
description: err instanceof Error ? err.message : 'unknown error',
|
|
188
|
+
color: 'error',
|
|
189
|
+
})
|
|
190
|
+
} finally {
|
|
191
|
+
importing.value = false
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
153
195
|
async function onImportFiles(event: Event) {
|
|
154
196
|
const target = event.target as HTMLInputElement
|
|
155
197
|
const files = Array.from(target.files ?? [])
|
|
@@ -312,14 +354,21 @@ async function undoTrashIds(ids: string[]) {
|
|
|
312
354
|
/>
|
|
313
355
|
</template>
|
|
314
356
|
<template #right>
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
357
|
+
<UDropdownMenu
|
|
358
|
+
:items="[
|
|
359
|
+
{ label: 'Pick .md files…', icon: 'i-lucide-file-up', onSelect: triggerImport },
|
|
360
|
+
{ label: 'From URLs…', icon: 'i-lucide-globe', onSelect: () => urlImportOpen = true },
|
|
361
|
+
]"
|
|
362
|
+
>
|
|
363
|
+
<UButton
|
|
364
|
+
label="Import"
|
|
365
|
+
icon="i-lucide-file-up"
|
|
366
|
+
variant="soft"
|
|
367
|
+
size="sm"
|
|
368
|
+
:loading="importing"
|
|
369
|
+
trailing-icon="i-lucide-chevron-down"
|
|
370
|
+
/>
|
|
371
|
+
</UDropdownMenu>
|
|
323
372
|
<input
|
|
324
373
|
ref="importInput"
|
|
325
374
|
type="file"
|
|
@@ -328,6 +377,38 @@ async function undoTrashIds(ids: string[]) {
|
|
|
328
377
|
class="hidden"
|
|
329
378
|
@change="onImportFiles"
|
|
330
379
|
/>
|
|
380
|
+
<UModal v-model:open="urlImportOpen" title="Import from URLs">
|
|
381
|
+
<template #content>
|
|
382
|
+
<UCard>
|
|
383
|
+
<template #header>
|
|
384
|
+
<h2 class="text-lg font-bold">Import personas from URLs</h2>
|
|
385
|
+
<p class="text-xs text-muted mt-0.5">
|
|
386
|
+
One raw .md URL per line. Files must have YAML
|
|
387
|
+
frontmatter with <code>type: persona</code>.
|
|
388
|
+
</p>
|
|
389
|
+
</template>
|
|
390
|
+
<UTextarea
|
|
391
|
+
v-model="urlImportText"
|
|
392
|
+
:rows="6"
|
|
393
|
+
placeholder="https://raw.githubusercontent.com/owner/repo/main/personas/alex.md"
|
|
394
|
+
class="w-full font-mono text-sm"
|
|
395
|
+
/>
|
|
396
|
+
<template #footer>
|
|
397
|
+
<div class="flex items-center justify-end gap-2">
|
|
398
|
+
<UButton label="Cancel" variant="ghost" :disabled="importing" @click="urlImportOpen = false" />
|
|
399
|
+
<UButton
|
|
400
|
+
label="Import"
|
|
401
|
+
icon="i-lucide-globe"
|
|
402
|
+
color="primary"
|
|
403
|
+
:loading="importing"
|
|
404
|
+
:disabled="!urlImportText.trim()"
|
|
405
|
+
@click="runUrlImport"
|
|
406
|
+
/>
|
|
407
|
+
</div>
|
|
408
|
+
</template>
|
|
409
|
+
</UCard>
|
|
410
|
+
</template>
|
|
411
|
+
</UModal>
|
|
331
412
|
<UButton
|
|
332
413
|
label="New Persona"
|
|
333
414
|
icon="i-lucide-plus"
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -2004,16 +2004,24 @@ def global_search(q: str = "", limit: int = 20):
|
|
|
2004
2004
|
def personas_import(body: dict):
|
|
2005
2005
|
"""Import persona Markdown files (frontmatter + body) into the store.
|
|
2006
2006
|
|
|
2007
|
-
Body:
|
|
2008
|
-
|
|
2007
|
+
Body:
|
|
2008
|
+
{"files": [{"name": "Alex.md", "content": "..."}]} # local picker
|
|
2009
|
+
{"urls": ["https://example.com/raw.md", ...]} # remote import
|
|
2010
|
+
|
|
2011
|
+
Both keys are accepted; URLs are fetched server-side (PR91b
|
|
2012
|
+
v3.36.0) and converted into the same `{name, content}` shape
|
|
2013
|
+
before processing.
|
|
2009
2014
|
|
|
2010
|
-
|
|
2011
|
-
lacking the frontmatter are flagged as failed without partial
|
|
2012
|
-
side-effects.
|
|
2015
|
+
Returns: {imported, failed, results: [{filename, status, id?, error?}]}
|
|
2013
2016
|
"""
|
|
2014
|
-
|
|
2015
|
-
|
|
2017
|
+
raw_files = body.get("files")
|
|
2018
|
+
raw_urls = body.get("urls")
|
|
2019
|
+
if raw_files is not None and not isinstance(raw_files, list):
|
|
2016
2020
|
return {"error": "files must be a list"}
|
|
2021
|
+
if raw_urls is not None and not isinstance(raw_urls, list):
|
|
2022
|
+
return {"error": "urls must be a list"}
|
|
2023
|
+
files = list(raw_files or [])
|
|
2024
|
+
urls = list(raw_urls or [])
|
|
2017
2025
|
mgr = _get_persona_manager()
|
|
2018
2026
|
if not mgr:
|
|
2019
2027
|
return {"error": "Persona manager unavailable"}
|
|
@@ -2022,6 +2030,10 @@ def personas_import(body: dict):
|
|
|
2022
2030
|
|
|
2023
2031
|
from core.personas.obsidian_store import ObsidianPersonaStore
|
|
2024
2032
|
|
|
2033
|
+
# Resolve URLs into {name, content} entries.
|
|
2034
|
+
if urls:
|
|
2035
|
+
files.extend(_fetch_url_entries(urls))
|
|
2036
|
+
|
|
2025
2037
|
imported = 0
|
|
2026
2038
|
failed = 0
|
|
2027
2039
|
results: list[dict] = []
|
|
@@ -2032,6 +2044,14 @@ def personas_import(body: dict):
|
|
|
2032
2044
|
continue
|
|
2033
2045
|
filename = str(entry.get("name") or "")
|
|
2034
2046
|
content = str(entry.get("content") or "")
|
|
2047
|
+
# Carry forward URL-fetch errors so the operator sees them.
|
|
2048
|
+
if entry.get("fetch_error"):
|
|
2049
|
+
failed += 1
|
|
2050
|
+
results.append({
|
|
2051
|
+
"filename": filename, "status": "failed",
|
|
2052
|
+
"error": str(entry["fetch_error"]),
|
|
2053
|
+
})
|
|
2054
|
+
continue
|
|
2035
2055
|
if not content.strip():
|
|
2036
2056
|
failed += 1
|
|
2037
2057
|
results.append({"filename": filename, "status": "failed", "error": "empty content"})
|
|
@@ -2058,6 +2078,38 @@ def personas_import(body: dict):
|
|
|
2058
2078
|
return {"imported": imported, "failed": failed, "results": results}
|
|
2059
2079
|
|
|
2060
2080
|
|
|
2081
|
+
def _fetch_url_entries(urls: list[str]) -> list[dict]:
|
|
2082
|
+
"""Fetch each URL and return ``{name, content}`` entries. PR91b."""
|
|
2083
|
+
import urllib.error
|
|
2084
|
+
import urllib.parse
|
|
2085
|
+
import urllib.request
|
|
2086
|
+
out: list[dict] = []
|
|
2087
|
+
for raw in urls:
|
|
2088
|
+
url = str(raw or "").strip()
|
|
2089
|
+
if not url:
|
|
2090
|
+
continue
|
|
2091
|
+
parsed = urllib.parse.urlparse(url)
|
|
2092
|
+
if parsed.scheme not in ("http", "https"):
|
|
2093
|
+
out.append({"name": url, "fetch_error": "scheme must be http(s)"})
|
|
2094
|
+
continue
|
|
2095
|
+
try:
|
|
2096
|
+
req = urllib.request.Request(url, headers={"User-Agent": "ArkaOS/persona-import"})
|
|
2097
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
2098
|
+
content = resp.read().decode("utf-8", errors="replace")
|
|
2099
|
+
except urllib.error.URLError as exc:
|
|
2100
|
+
out.append({"name": url, "fetch_error": f"fetch failed: {exc.reason}"})
|
|
2101
|
+
continue
|
|
2102
|
+
except (TimeoutError, OSError) as exc:
|
|
2103
|
+
out.append({"name": url, "fetch_error": f"fetch failed: {exc}"})
|
|
2104
|
+
continue
|
|
2105
|
+
# Derive a filename from the URL path.
|
|
2106
|
+
name = (parsed.path.rsplit("/", 1)[-1] or "imported.md").strip()
|
|
2107
|
+
if not name.endswith(".md"):
|
|
2108
|
+
name = f"{name or 'imported'}.md"
|
|
2109
|
+
out.append({"name": name, "content": content})
|
|
2110
|
+
return out
|
|
2111
|
+
|
|
2112
|
+
|
|
2061
2113
|
# --- Trash / Undo (PR85b v3.12.0) ---
|
|
2062
2114
|
|
|
2063
2115
|
@app.get("/api/trash")
|