Changeset 0.12.0 (#63)

This commit is contained in:
2026-02-25 21:38:49 +00:00
parent c9d8e9457e
commit 88216ec4cb
59 changed files with 13115 additions and 139 deletions

View File

@@ -226,7 +226,7 @@ const API = {
// ── Completions ──────────────────────────
async streamCompletion(channelId, content, model, signal, apiConfigId, presetId) {
async streamCompletion(channelId, content, model, signal, apiConfigId, presetId, attachmentIds) {
const body = { channel_id: channelId, content, stream: true };
if (presetId) {
body.preset_id = presetId;
@@ -238,6 +238,7 @@ const API = {
if (apiConfigId) body.provider_config_id = apiConfigId;
// Only send max_tokens if user explicitly set it (non-zero = override)
if (App.settings.maxTokens > 0) body.max_tokens = App.settings.maxTokens;
if (attachmentIds?.length) body.attachment_ids = attachmentIds;
let resp = await fetch(BASE + '/api/v1/chat/completions', {
method: 'POST',
@@ -455,6 +456,66 @@ const API = {
searchNotes(query, limit = 20) { return this._get(`/api/v1/notes/search?q=${encodeURIComponent(query)}&limit=${limit}`); },
listNoteFolders() { return this._get('/api/v1/notes/folders'); },
// ── Attachments ─────────────────────────
/**
* Upload a file to a channel. Uses multipart/form-data (not JSON).
* Returns the created attachment object with id, metadata, etc.
*/
async uploadAttachment(channelId, file) {
const form = new FormData();
form.append('file', file, file.name);
const doFetch = () => fetch(BASE + `/api/v1/channels/${channelId}/attachments`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${this.accessToken}` },
// No Content-Type — browser sets multipart boundary automatically
body: form,
});
let resp = await doFetch();
if (resp.status === 401 && this.refreshToken) {
if (await this.refresh()) resp = await doFetch();
}
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
const err = new Error(data.error || `Upload failed: HTTP ${resp.status}`);
err.status = resp.status;
throw err;
}
return this._parseJSON(resp, `/api/v1/channels/${channelId}/attachments`);
},
getAttachment(id) { return this._get(`/api/v1/attachments/${id}`); },
async downloadAttachmentBlob(id) {
const doFetch = () => fetch(BASE + `/api/v1/attachments/${id}/download`, {
headers: { 'Authorization': `Bearer ${this.accessToken}` },
});
let resp = await doFetch();
if (resp.status === 401 && this.refreshToken) {
if (await this.refresh()) resp = await doFetch();
}
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data.error || `Download failed: HTTP ${resp.status}`);
}
return resp.blob();
},
deleteAttachment(id) { return this._del(`/api/v1/attachments/${id}`); },
listChannelAttachments(channelId) { return this._get(`/api/v1/channels/${channelId}/attachments`); },
// ── Admin Storage ───────────────────────
adminGetStorageStatus() { return this._get('/api/v1/admin/storage/status'); },
adminGetOrphanCount() { return this._get('/api/v1/admin/storage/orphans'); },
adminRunStorageCleanup() { return this._post('/api/v1/admin/storage/cleanup', {}); },
adminGetExtractionStatus() { return this._get('/api/v1/admin/storage/extraction'); },
// Vault
adminGetVaultStatus() { return this._get('/api/v1/admin/vault/status'); },
// ── Admin Roles ─────────────────────────
adminListRoles() { return this._get('/api/v1/admin/roles'); },
adminGetRole(role) { return this._get(`/api/v1/admin/roles/${role}`); },