diff --git a/collector/extensions/index.js b/collector/extensions/index.js index 087df6f21f..76c5aafd33 100644 --- a/collector/extensions/index.js +++ b/collector/extensions/index.js @@ -4,6 +4,7 @@ const { resolveRepoLoader, resolveRepoLoaderFunction } = require("../utils/exten const { reqBody } = require("../utils/http"); const { validURL } = require("../utils/url"); const RESYNC_METHODS = require("./resync"); +const { loadObsidianVault } = require("../utils/extensions/ObsidianVault"); function extensions(app) { if (!app) return; @@ -180,6 +181,27 @@ function extensions(app) { return; } ); + + app.post( + "/ext/obsidian/vault", + [verifyPayloadIntegrity, setDataSigner], + async function (request, response) { + try { + const { files } = reqBody(request); + const result = await loadObsidianVault({ files }); + response.status(200).json(result); + } catch (e) { + console.error(e); + response.status(400).json({ + success: false, + reason: e.message, + data: null, + }); + } + return; + } + ); } -module.exports = extensions; + +module.exports = extensions; \ No newline at end of file diff --git a/collector/utils/extensions/ObsidianVault/index.js b/collector/utils/extensions/ObsidianVault/index.js new file mode 100644 index 0000000000..18d62be3ac --- /dev/null +++ b/collector/utils/extensions/ObsidianVault/index.js @@ -0,0 +1,91 @@ +const { v4 } = require("uuid"); +const { default: slugify } = require("slugify"); +const path = require("path"); +const fs = require("fs"); +const { + writeToServerDocuments, + sanitizeFileName, + documentsFolder, +} = require("../../files"); + +function parseObsidianVaultPath(files = []) { + const possiblePaths = new Set(); + files.forEach( + (file) => file?.path && possiblePaths.add(file.path.split("/")[0]) + ); + + switch (possiblePaths.size) { + case 0: + return null; + case 1: + // The user specified a vault properly - so all files are in the same folder. + return possiblePaths.values().next().value; + default: + return null; + } +} + +async function loadObsidianVault({ files = [] }) { + if (!files || files?.length === 0) + return { success: false, error: "No files provided" }; + const vaultName = parseObsidianVaultPath(files); + const folderUUId = v4().slice(0, 4); + const outFolder = vaultName + ? slugify(`obsidian-vault-${vaultName}-${folderUUId}`).toLowerCase() + : slugify(`obsidian-${folderUUId}`).toLowerCase(); + const outFolderPath = path.resolve(documentsFolder, outFolder); + if (!fs.existsSync(outFolderPath)) + fs.mkdirSync(outFolderPath, { recursive: true }); + + console.log( + `Processing ${files.length} files from Obsidian Vault ${ + vaultName ? `"${vaultName}"` : "" + }` + ); + const results = []; + for (const file of files) { + try { + const fullPageContent = file?.content; + // If the file has no content or is just whitespace, skip it. + if (!fullPageContent || fullPageContent.trim() === "") continue; + + const data = { + id: v4(), + url: `obsidian://${file.path}`, + title: file.name, + docAuthor: "Obsidian Vault", + description: file.name, + docSource: "Obsidian Vault", + chunkSource: `obsidian://${file.path}`, + published: new Date().toLocaleString(), + wordCount: fullPageContent.split(" ").length, + pageContent: fullPageContent, + token_count_estimate: fullPageContent.length / 4, // rough estimate + }; + + const targetFileName = sanitizeFileName( + `${slugify(file.name)}-${data.id}` + ); + writeToServerDocuments(data, targetFileName, outFolderPath); + results.push({ file: file.path, status: "success" }); + } catch (e) { + console.error(`Failed to process ${file.path}:`, e); + results.push({ file: file.path, status: "failed", reason: e.message }); + } + } + + return { + success: true, + data: { + processed: results.filter((r) => r.status === "success").length, + failed: results.filter((r) => r.status === "failed").length, + total: files.length, + results, + destination: path.basename(outFolderPath), + }, + }; +} + +module.exports = { + loadObsidianVault, +}; diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index edea280da5..14b6cf9215 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -2,6 +2,15 @@ const fs = require("fs"); const path = require("path"); const { MimeDetector } = require("./mime"); +/** + * The folder where documents are stored to be stored when + * processed by the collector. + */ +const documentsFolder = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../../server/storage/documents`) + : path.resolve(process.env.STORAGE_DIR, `documents`); + /** * Checks if a file is text by checking the mime type and then falling back to buffer inspection. * This way we can capture all the cases where the mime type is not known but still parseable as text @@ -189,4 +198,5 @@ module.exports = { normalizePath, isWithin, sanitizeFileName, + documentsFolder, }; diff --git a/frontend/src/components/DataConnectorOption/media/index.js b/frontend/src/components/DataConnectorOption/media/index.js index 23d62b8c5e..b0fba91bc4 100644 --- a/frontend/src/components/DataConnectorOption/media/index.js +++ b/frontend/src/components/DataConnectorOption/media/index.js @@ -4,6 +4,7 @@ import YouTube from "./youtube.svg"; import Link from "./link.svg"; import Confluence from "./confluence.jpeg"; import DrupalWiki from "./drupalwiki.jpg"; +import Obsidian from "./obsidian.png"; const ConnectorImages = { github: GitHub, @@ -12,6 +13,7 @@ const ConnectorImages = { websiteDepth: Link, confluence: Confluence, drupalwiki: DrupalWiki, + obsidian: Obsidian, }; export default ConnectorImages; diff --git a/frontend/src/components/DataConnectorOption/media/obsidian.png b/frontend/src/components/DataConnectorOption/media/obsidian.png new file mode 100644 index 0000000000..4fd6036b37 Binary files /dev/null and b/frontend/src/components/DataConnectorOption/media/obsidian.png differ diff --git a/frontend/src/components/Modals/ManageWorkspace/DataConnectors/Connectors/Obsidian/index.jsx b/frontend/src/components/Modals/ManageWorkspace/DataConnectors/Connectors/Obsidian/index.jsx new file mode 100644 index 0000000000..e82705591f --- /dev/null +++ b/frontend/src/components/Modals/ManageWorkspace/DataConnectors/Connectors/Obsidian/index.jsx @@ -0,0 +1,175 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FolderOpen, Info } from "@phosphor-icons/react"; +import System from "@/models/system"; +import showToast from "@/utils/toast"; + +export default function ObsidianOptions() { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [vaultPath, setVaultPath] = useState(""); + const [selectedFiles, setSelectedFiles] = useState([]); + + const handleFolderPick = async (e) => { + const files = Array.from(e.target.files); + if (files.length === 0) return; + + // Filter for .md files only + const markdownFiles = files.filter((file) => file.name.endsWith(".md")); + setSelectedFiles(markdownFiles); + + // Set the folder path from the first file + if (markdownFiles.length > 0) { + const path = markdownFiles[0].webkitRelativePath.split("/")[0]; + setVaultPath(path); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + if (selectedFiles.length === 0) return; + + try { + setLoading(true); + showToast("Importing Obsidian vault - this may take a while.", "info", { + clear: true, + autoClose: false, + }); + + // Read all files and prepare them for submission + const fileContents = await Promise.all( + selectedFiles.map(async (file) => { + const content = await file.text(); + return { + name: file.name, + path: file.webkitRelativePath, + content: content, + }; + }) + ); + + const { data, error } = await System.dataConnectors.obsidian.collect({ + files: fileContents, + }); + + if (!!error) { + showToast(error, "error", { clear: true }); + setLoading(false); + setSelectedFiles([]); + setVaultPath(""); + return; + } + + // Show results + const successCount = data.processed; + const failCount = data.failed; + const totalCount = data.total; + + if (successCount === totalCount) { + showToast( + `Successfully imported ${successCount} files from your vault!`, + "success", + { clear: true } + ); + } else { + showToast( + `Imported ${successCount} files, ${failCount} failed`, + "warning", + { clear: true } + ); + } + + setLoading(false); + } catch (e) { + console.error(e); + showToast(e.message, "error", { clear: true }); + setLoading(false); + } + }; + + return ( +
+
+
+
+
+
+
+ +

+ {t("connectors.obsidian.vault_warning")} +

+
+
+ +
+
+ +

+ {t("connectors.obsidian.vault_description")} +

+
+
+ setVaultPath(e.target.value)} + placeholder="/path/to/your/vault" + className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5" + required={true} + autoComplete="off" + spellCheck={false} + readOnly + /> + +
+ {selectedFiles.length > 0 && ( + <> +

+ {t("connectors.obsidian.selected_files", { + count: selectedFiles.length, + })} +

+ + {selectedFiles.map((file, i) => ( +

+ {file.webkitRelativePath} +

+ ))} + + )} +
+
+
+ +
+ + {loading && ( +

+ {t("connectors.obsidian.processing_time")} +

+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/components/Modals/ManageWorkspace/DataConnectors/index.jsx b/frontend/src/components/Modals/ManageWorkspace/DataConnectors/index.jsx index 4e6470b281..1e1d268525 100644 --- a/frontend/src/components/Modals/ManageWorkspace/DataConnectors/index.jsx +++ b/frontend/src/components/Modals/ManageWorkspace/DataConnectors/index.jsx @@ -9,6 +9,7 @@ import DrupalWikiOptions from "./Connectors/DrupalWiki"; import { useState } from "react"; import ConnectorOption from "./ConnectorOption"; import WebsiteDepthOptions from "./Connectors/WebsiteDepth"; +import ObsidianOptions from "./Connectors/Obsidian"; export const getDataConnectors = (t) => ({ github: { @@ -47,6 +48,12 @@ export const getDataConnectors = (t) => ({ description: "Import Drupal Wiki spaces in a single click.", options: , }, + obsidian: { + name: "Obsidian", + image: ConnectorImages.obsidian, + description: "Import Obsidian vault in a single click.", + options: , + }, }); export default function DataConnectors() { diff --git a/frontend/src/locales/ar/common.js b/frontend/src/locales/ar/common.js index 1d9dcd0906..93d9ced629 100644 --- a/frontend/src/locales/ar/common.js +++ b/frontend/src/locales/ar/common.js @@ -641,6 +641,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js index ecb556e742..719cbe3e63 100644 --- a/frontend/src/locales/da/common.js +++ b/frontend/src/locales/da/common.js @@ -679,6 +679,17 @@ const TRANSLATIONS = { watch_explained_block3_end: " adminvisning.", accept: "Okay, jeg har forstået", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: "Velkommen til dit nye arbejdsområde.", diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js index 032957b851..de025eeabc 100644 --- a/frontend/src/locales/de/common.js +++ b/frontend/src/locales/de/common.js @@ -677,6 +677,17 @@ const TRANSLATIONS = { " entscheiden, welche Dokumente du beobachten möchtest.", accept: "Alles klar, ich habe es verstanden.", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: "Willkommen zu deinem Arbeitsbereich.", diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index f24fff9ec1..d91091973b 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -721,6 +721,20 @@ const TRANSLATIONS = { connectors: { "search-placeholder": "Search data connectors", "no-connectors": "No data connectors found.", + obsidian: { + name: "Obsidian", + description: "Import Obsidian vault in a single click.", + vault_location: "Vault Location", + vault_description: + "Select your Obsidian vault folder to import all notes and their connections.", + selected_files: "Found {{count}} markdown files", + importing: "Importing vault...", + import_vault: "Import Vault", + processing_time: + "This may take a while depending on the size of your vault.", + vault_warning: + "To avoid any conflicts, make sure your Obsidian vault is not currently open.", + }, github: { name: "GitHub Repo", description: diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js index 019454c1f7..82ad138fbe 100644 --- a/frontend/src/locales/es/common.js +++ b/frontend/src/locales/es/common.js @@ -640,6 +640,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js index 98b88e5808..13977f0a4f 100644 --- a/frontend/src/locales/fa/common.js +++ b/frontend/src/locales/fa/common.js @@ -633,6 +633,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js index 76b89d9c06..dcaa5b0757 100644 --- a/frontend/src/locales/fr/common.js +++ b/frontend/src/locales/fr/common.js @@ -641,6 +641,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js index 7e8abd7a32..63d4c3cc23 100644 --- a/frontend/src/locales/he/common.js +++ b/frontend/src/locales/he/common.js @@ -626,6 +626,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js index f2030a67d3..08fc2f35ca 100644 --- a/frontend/src/locales/it/common.js +++ b/frontend/src/locales/it/common.js @@ -639,6 +639,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js index 84a4bf8d48..bd629a0b78 100644 --- a/frontend/src/locales/ja/common.js +++ b/frontend/src/locales/ja/common.js @@ -671,6 +671,17 @@ const TRANSLATIONS = { watch_explained_block3_end: "管理画面から行えます。", accept: "わかりました", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: "新しいワークスペースへようこそ。", diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js index c83e0ad2a3..9fb796c9ae 100644 --- a/frontend/src/locales/ko/common.js +++ b/frontend/src/locales/ko/common.js @@ -626,6 +626,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js index 1dbb3bc72d..0472ebdd62 100644 --- a/frontend/src/locales/nl/common.js +++ b/frontend/src/locales/nl/common.js @@ -636,6 +636,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js index 9262deafd4..d124e65949 100644 --- a/frontend/src/locales/pt_BR/common.js +++ b/frontend/src/locales/pt_BR/common.js @@ -645,6 +645,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js index 8cc1c94b68..c0bc9eb834 100644 --- a/frontend/src/locales/ru/common.js +++ b/frontend/src/locales/ru/common.js @@ -680,6 +680,17 @@ const TRANSLATIONS = { watch_explained_block3_end: " в режиме администратора.", accept: "Хорошо, понял", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: "Добро пожаловать в ваше новое рабочее пространство.", diff --git a/frontend/src/locales/tr/common.js b/frontend/src/locales/tr/common.js index 78ad528ea3..969d986da5 100644 --- a/frontend/src/locales/tr/common.js +++ b/frontend/src/locales/tr/common.js @@ -636,6 +636,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js index f22e1895c9..ba6285a52e 100644 --- a/frontend/src/locales/vn/common.js +++ b/frontend/src/locales/vn/common.js @@ -635,6 +635,17 @@ const TRANSLATIONS = { watch_explained_block3_end: null, accept: null, }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: null, diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js index eb4f02d3b0..3abd921d7a 100644 --- a/frontend/src/locales/zh/common.js +++ b/frontend/src/locales/zh/common.js @@ -66,7 +66,6 @@ const TRANSLATIONS = { yes: "是", no: "否", }, - settings: { title: "设置", system: "系统", @@ -221,12 +220,10 @@ const TRANSLATIONS = { }, }, }, - "new-workspace": { title: "新工作区", placeholder: "我的工作区", }, - "workspaces—settings": { general: "通用设置", chat: "聊天设置", @@ -234,7 +231,6 @@ const TRANSLATIONS = { members: "成员", agent: "代理配置", }, - general: { vector: { title: "向量数量", @@ -267,7 +263,6 @@ const TRANSLATIONS = { "工作区。这将删除矢量数据库中的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。", }, }, - chat: { llm: { title: "工作区 LLM 提供者", @@ -332,7 +327,6 @@ const TRANSLATIONS = { hint: "大多数 LLM 都有各种可接受的有效值范围。请咨询你的LLM提供商以获取该信息。", }, }, - "vector-workspace": { identifier: "向量数据库标识符", snippets: { @@ -359,7 +353,6 @@ const TRANSLATIONS = { error: "无法重置工作区向量数据库!", }, }, - agent: { "performance-warning": "不明确支持工具调用的 LLMs 的性能高度依赖于模型的功能和准确性。有些能力可能受到限制或不起作用。", @@ -376,7 +369,6 @@ const TRANSLATIONS = { description: "将用于此工作区的 @agent 代理的特定 LLM 模型。", wait: "-- 等待模型 --", }, - skill: { title: "默认代理技能", description: @@ -411,7 +403,6 @@ const TRANSLATIONS = { }, }, }, - recorded: { title: "工作区聊天历史记录", description: "这些是用户发送的所有聊天记录和消息,按创建日期排序。", @@ -425,7 +416,6 @@ const TRANSLATIONS = { at: "发送时间", }, }, - customization: { interface: { title: "界面偏好设置", @@ -515,7 +505,6 @@ const TRANSLATIONS = { }, }, }, - api: { title: "API 密钥", description: "API 密钥允许持有者以编程方式访问和管理此 AnythingLLM 实例。", @@ -527,14 +516,12 @@ const TRANSLATIONS = { created: "创建时间", }, }, - llm: { title: "LLM 首选项", description: "这些是你首选的 LLM 聊天和嵌入提供商的凭据和设置。重要的是,确保这些密钥是最新的和正确的,否则 AnythingLLM 将无法正常运行。", provider: "LLM 提供商", }, - transcription: { title: "转录模型首选项", description: @@ -545,7 +532,6 @@ const TRANSLATIONS = { "warn-recommend": "我们建议至少 2GB RAM 并上传 <10Mb 的文件。", "warn-end": "内置模型将在首次使用时自动下载。", }, - embedding: { title: "嵌入首选项", "desc-start": @@ -557,7 +543,6 @@ const TRANSLATIONS = { description: "使用 AnythingLLM 的本机嵌入引擎时不需要设置。", }, }, - text: { title: "文本拆分和分块首选项", "desc-start": @@ -571,13 +556,11 @@ const TRANSLATIONS = { description: "这是单个向量中可以存在的字符的最大长度。", recommend: "嵌入模型的最大长度为", }, - overlap: { title: "文本块重叠", description: "这是在两个相邻文本块之间分块期间发生的最大字符重叠。", }, }, - vector: { title: "向量数据库", description: @@ -587,7 +570,6 @@ const TRANSLATIONS = { description: "LanceDB 不需要任何配置。", }, }, - embeddable: { title: "可嵌入的聊天小部件", description: @@ -599,7 +581,6 @@ const TRANSLATIONS = { Active: "活动域", }, }, - "embed-chats": { title: "嵌入的聊天历史纪录", export: "导出", @@ -612,7 +593,6 @@ const TRANSLATIONS = { at: "发送时间", }, }, - multi: { title: "多用户模式", description: "通过激活多用户模式来设置你的实例以支持你的团队。", @@ -636,7 +616,6 @@ const TRANSLATIONS = { password: "实例密码", }, }, - event: { title: "事件日志", description: "查看此实例上发生的所有操作和事件以进行监控。", @@ -647,7 +626,6 @@ const TRANSLATIONS = { occurred: "发生时间", }, }, - privacy: { title: "隐私和数据处理", description: @@ -657,7 +635,6 @@ const TRANSLATIONS = { vector: "向量数据库", anonymous: "启用匿名遥测", }, - connectors: { "search-placeholder": "搜索数据连接器", "no-connectors": "未找到数据连接器。", @@ -764,7 +741,6 @@ const TRANSLATIONS = { pat_token_explained: "您的 Confluence 个人访问令牌。", task_explained: "完成后,页面内容将可用于在文档选择器中嵌入至工作区。", }, - manage: { documents: "文档", "data-connectors": "数据连接器", @@ -828,8 +804,18 @@ const TRANSLATIONS = { watch_explained_block3_end: " 管理视图中管理被监控的文档。", accept: "好的,知道了", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, - chat_window: { welcome: "欢迎来到你的新工作区。", get_started: "开始使用,请先", @@ -845,7 +831,6 @@ const TRANSLATIONS = { microphone: "语音输入你的提示。", send: "将提示消息发送到工作区", }, - profile_settings: { edit_account: "编辑帐户", profile_picture: "头像", diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js index 7a6cd0c488..4121fb68ff 100644 --- a/frontend/src/locales/zh_TW/common.js +++ b/frontend/src/locales/zh_TW/common.js @@ -638,6 +638,17 @@ const TRANSLATIONS = { watch_explained_block3_end: " 管理觀看的文件。", accept: "好的,明白了", }, + obsidian: { + name: null, + description: null, + vault_location: null, + vault_description: null, + selected_files: null, + importing: null, + import_vault: null, + processing_time: null, + vault_warning: null, + }, }, chat_window: { welcome: "歡迎使用您的新工作區。", diff --git a/frontend/src/models/dataConnector.js b/frontend/src/models/dataConnector.js index 302826e48a..c76afaa2c0 100644 --- a/frontend/src/models/dataConnector.js +++ b/frontend/src/models/dataConnector.js @@ -187,6 +187,26 @@ const DataConnector = { }); }, }, + obsidian: { + collect: async function ({ files }) { + return await fetch(`${API_BASE}/ext/obsidian/vault`, { + method: "POST", + headers: baseHeaders(), + body: JSON.stringify({ + files, + }), + }) + .then((res) => res.json()) + .then((res) => { + if (!res.success) throw new Error(res.reason); + return { data: res.data, error: null }; + }) + .catch((e) => { + console.error(e); + return { data: null, error: e.message }; + }); + }, + }, }; export default DataConnector; diff --git a/server/endpoints/extensions/index.js b/server/endpoints/extensions/index.js index 7bfff06724..0a4acea6cc 100644 --- a/server/endpoints/extensions/index.js +++ b/server/endpoints/extensions/index.js @@ -148,6 +148,28 @@ function extensionEndpoints(app) { } } ); + + app.post( + "/ext/obsidian/vault", + [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], + async (request, response) => { + try { + const responseFromProcessor = + await new CollectorApi().forwardExtensionRequest({ + endpoint: "/ext/obsidian/vault", + method: "POST", + body: request.body, + }); + await Telemetry.sendTelemetry("extension_invoked", { + type: "obsidian_vault", + }); + response.status(200).json(responseFromProcessor); + } catch (e) { + console.error(e); + response.sendStatus(500).end(); + } + } + ); } module.exports = { extensionEndpoints };