ken_nogi/lineworks-sync/anythingllm-client.js
Kenichiro NOGI 88a402ce0f up
2026-07-10 18:13:30 +09:00

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,
};