Changeset 0.12.0 (#63)
This commit is contained in:
@@ -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}`); },
|
||||
|
||||
Reference in New Issue
Block a user