145 lines
5.6 KiB
JavaScript
145 lines
5.6 KiB
JavaScript
"use strict";
|
|
|
|
/*
|
|
* AnythingLLM アップロード共通処理。
|
|
* lineworks-anythingllm.js のアップロード/埋め込みロジックを、
|
|
* workspaceSlug 等を引数化して csv-anythingllm.js から再利用できるようにしたもの。
|
|
* lineworks-anythingllm.js 自体は変更していない(既存の動作への影響を避けるため)。
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
// 拡張子からMIMEタイプを簡易判定 (AnythingLLM側の処理分岐に必要)
|
|
function guessMimeType(filename) {
|
|
const ext = path.extname(filename).toLowerCase();
|
|
const map = {
|
|
".pdf": "application/pdf",
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
".txt": "text/plain",
|
|
".md": "text/markdown",
|
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
".csv": "text/csv",
|
|
};
|
|
return map[ext] || "application/octet-stream";
|
|
}
|
|
|
|
async function uploadFile(filepath, { baseUrl, apiKey, mimeType }) {
|
|
const fileBuffer = fs.readFileSync(filepath);
|
|
const blob = new Blob([fileBuffer], { type: mimeType || guessMimeType(filepath) });
|
|
|
|
const form = new FormData();
|
|
form.append("file", blob, path.basename(filepath));
|
|
|
|
const res = await fetch(`${baseUrl}/api/v1/document/upload`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
body: form,
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`AnythingLLMアップロード失敗: ${res.status} ${await res.text()}`);
|
|
}
|
|
const data = await res.json();
|
|
return data.documents?.[0]?.location;
|
|
}
|
|
|
|
// 一度に大量のファイルを埋め込もうとするとAnythingLLM/Ollama側でタイムアウトする
|
|
// (fetch failed) ため、一定件数ごとにバッチ分割して順番に送信する
|
|
async function addToWorkspaceEmbeddings(locations, { baseUrl, apiKey, workspaceSlug, batchSize = 50 }) {
|
|
const totalBatches = Math.ceil(locations.length / batchSize);
|
|
let succeededCount = 0;
|
|
const failedBatches = [];
|
|
|
|
for (let i = 0; i < locations.length; i += batchSize) {
|
|
const batch = locations.slice(i, i + batchSize);
|
|
const batchNumber = Math.floor(i / batchSize) + 1;
|
|
|
|
console.log(` 埋め込み中... バッチ ${batchNumber}/${totalBatches} (${batch.length}件)`);
|
|
|
|
try {
|
|
const res = await fetch(`${baseUrl}/api/v1/workspace/${workspaceSlug}/update-embeddings`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ adds: batch, deletes: [] }),
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`${res.status} ${await res.text()}`);
|
|
}
|
|
succeededCount += batch.length;
|
|
} catch (err) {
|
|
console.warn(` [警告] バッチ ${batchNumber} の埋め込みに失敗: ${err.message}`);
|
|
failedBatches.push({ batchNumber, count: batch.length, reason: err.message });
|
|
}
|
|
}
|
|
|
|
console.log(`埋め込み完了: 成功 ${succeededCount}/${locations.length} 件`);
|
|
if (failedBatches.length > 0) {
|
|
console.warn(`失敗したバッチ: ${failedBatches.length}件`);
|
|
failedBatches.forEach((b) => console.warn(` - バッチ${b.batchNumber} (${b.count}件): ${b.reason}`));
|
|
}
|
|
|
|
return { succeededCount, failedBatches };
|
|
}
|
|
|
|
async function listWorkspaces({ baseUrl, apiKey }) {
|
|
const res = await fetch(`${baseUrl}/api/v1/workspaces`, {
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`ワークスペース一覧の取得に失敗: ${res.status} ${await res.text()}`);
|
|
}
|
|
const data = await res.json();
|
|
return data.workspaces || [];
|
|
}
|
|
|
|
async function createWorkspace(name, { baseUrl, apiKey }) {
|
|
const res = await fetch(`${baseUrl}/api/v1/workspace/new`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`ワークスペース作成に失敗: ${res.status} ${await res.text()}`);
|
|
}
|
|
const data = await res.json();
|
|
return data.workspace;
|
|
}
|
|
|
|
// 指定した名前/slugのワークスペースが存在すればそのslugを、無ければ新規作成して
|
|
// 作成後の実際のslugを返す(AnythingLLMはslugを名前から自動生成するため、
|
|
// 希望した文字列と完全一致しない場合がある)。
|
|
async function ensureWorkspaceExists(name, { baseUrl, apiKey }) {
|
|
const workspaces = await listWorkspaces({ baseUrl, apiKey });
|
|
const existing = workspaces.find((w) => w.slug === name || w.name === name);
|
|
if (existing) {
|
|
return existing.slug;
|
|
}
|
|
|
|
console.log(` ワークスペース「${name}」が見つからないため新規作成します...`);
|
|
const created = await createWorkspace(name, { baseUrl, apiKey });
|
|
console.log(` ワークスペースを作成しました: ${created.name} (slug: ${created.slug})`);
|
|
return created.slug;
|
|
}
|
|
|
|
module.exports = {
|
|
uploadFile,
|
|
addToWorkspaceEmbeddings,
|
|
guessMimeType,
|
|
listWorkspaces,
|
|
createWorkspace,
|
|
ensureWorkspaceExists,
|
|
};
|