feat: 增加导出话题至Notion的功能 (#1331)

* feat: 新增导出至Notion的选项

* fix:添加多语言支持

* fix:添加提示语的多语言支持,以及防止重复导入的状态

* fix:修复多语言错误及调整UI样式统一
This commit is contained in:
Trey Dong 2025-02-11 11:27:01 +08:00 committed by GitHub
parent 1d82552491
commit 50cc1c6b5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 220 additions and 19 deletions

View File

@ -61,6 +61,7 @@
"@llm-tools/embedjs-loader-web": "^0.1.25", "@llm-tools/embedjs-loader-web": "^0.1.25",
"@llm-tools/embedjs-loader-xml": "^0.1.25", "@llm-tools/embedjs-loader-xml": "^0.1.25",
"@llm-tools/embedjs-openai": "^0.1.25", "@llm-tools/embedjs-openai": "^0.1.25",
"@notionhq/client": "^2.2.15",
"@types/react-infinite-scroll-component": "^5.0.0", "@types/react-infinite-scroll-component": "^5.0.0",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"apache-arrow": "^18.1.0", "apache-arrow": "^18.1.0",

View File

@ -115,6 +115,7 @@
"topics.edit.placeholder": "Enter new name", "topics.edit.placeholder": "Enter new name",
"topics.edit.title": "Edit Name", "topics.edit.title": "Edit Name",
"topics.export.image": "Export as image", "topics.export.image": "Export as image",
"topics.export.notion": "Export to Notion",
"topics.export.md": "Export as markdown", "topics.export.md": "Export as markdown",
"topics.export.title": "Export", "topics.export.title": "Export",
"topics.export.word": "Export as Word", "topics.export.word": "Export as Word",
@ -296,7 +297,11 @@
"error.get_embedding_dimensions": "Failed to get embedding dimensions", "error.get_embedding_dimensions": "Failed to get embedding dimensions",
"group.delete.title": "Delete Group Message", "group.delete.title": "Delete Group Message",
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers", "group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
"mention.title": "Switch model answer" "mention.title": "Switch model answer",
"error.notion.export": "Notion import failed",
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
"success.notion.export": "Notion import successful",
"warn.notion.exporting": "Notion is importing, please do not import repeatedly"
}, },
"minapp": { "minapp": {
"title": "MinApp", "title": "MinApp",
@ -421,7 +426,11 @@
"webdav.autoSync.off": "Off", "webdav.autoSync.off": "Off",
"webdav.noSync": "Waiting for next backup", "webdav.noSync": "Waiting for next backup",
"webdav.syncError": "Backup Error", "webdav.syncError": "Backup Error",
"webdav.lastSync": "Last Backup" "webdav.lastSync": "Last Backup",
"notion.api_key":"Notion API Key",
"notion.database_id":"Notion Database ID",
"notion.title":"Notion Configuration"
}, },
"quickAssistant": { "quickAssistant": {
"title": "Quick Assistant", "title": "Quick Assistant",

View File

@ -111,6 +111,7 @@
"topics.edit.title": "名前を編集", "topics.edit.title": "名前を編集",
"topics.export.image": "画像としてエクスポート", "topics.export.image": "画像としてエクスポート",
"topics.export.md": "Markdownとしてエクスポート", "topics.export.md": "Markdownとしてエクスポート",
"topics.export.notion": "Notion にエクスポート",
"topics.export.title": "エクスポート", "topics.export.title": "エクスポート",
"topics.export.word": "Wordとしてエクスポート", "topics.export.word": "Wordとしてエクスポート",
"topics.list": "トピックリスト", "topics.list": "トピックリスト",
@ -290,7 +291,11 @@
"error.get_embedding_dimensions": "埋込み次元を取得できませんでした", "error.get_embedding_dimensions": "埋込み次元を取得できませんでした",
"group.delete.title": "分組メッセージを削除", "group.delete.title": "分組メッセージを削除",
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます", "group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
"mention.title": "モデルを切り替える" "mention.title": "モデルを切り替える",
"error.notion.export": "Notion インポートに失敗",
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"success.notion.export": "Notion へのインポートに成功",
"warn.notion.exporting": "Notion 正在インポート中です。重複インポートしないでください。"
}, },
"minapp": { "minapp": {
"title": "ミニアプリ", "title": "ミニアプリ",
@ -413,7 +418,10 @@
"webdav.autoSync.off": "オフ", "webdav.autoSync.off": "オフ",
"webdav.noSync": "次回のバックアップを待っています", "webdav.noSync": "次回のバックアップを待っています",
"webdav.syncError": "バックアップエラー", "webdav.syncError": "バックアップエラー",
"webdav.lastSync": "最終同期" "webdav.lastSync": "最終同期",
"notion.api_key":"Notion APIキー",
"notion.database_id":"Notion データベースID",
"notion.title":"Notion 設定"
}, },
"quickAssistant": { "quickAssistant": {
"title": "クイックアシスタント", "title": "クイックアシスタント",

View File

@ -111,6 +111,7 @@
"topics.edit.title": "Редактировать заголовок", "topics.edit.title": "Редактировать заголовок",
"topics.export.image": "Экспорт как изображение", "topics.export.image": "Экспорт как изображение",
"topics.export.md": "Экспорт как markdown", "topics.export.md": "Экспорт как markdown",
"topics.export.notion": "Экспорт в Notion",
"topics.export.title": "Экспорт", "topics.export.title": "Экспорт",
"topics.export.word": "Экспорт как Word", "topics.export.word": "Экспорт как Word",
"topics.list": "Список топиков", "topics.list": "Список топиков",
@ -291,7 +292,11 @@
"error.get_embedding_dimensions": "Не удалось получить размерность встраивания", "error.get_embedding_dimensions": "Не удалось получить размерность встраивания",
"group.delete.title": "Удалить группу сообщений", "group.delete.title": "Удалить группу сообщений",
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника", "group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
"mention.title": "Переключить модель ответа" "mention.title": "Переключить модель ответа",
"error.notion.export": "Импорт в Notion не удался",
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"success.notion.export": "Импорт в Notion выполнен успешно",
"warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт"
}, },
"minapp": { "minapp": {
"title": "Встроенные приложения", "title": "Встроенные приложения",
@ -414,7 +419,10 @@
"webdav.autoSync.off": "Выключено", "webdav.autoSync.off": "Выключено",
"webdav.noSync": "Ожидание следующего резервного копирования", "webdav.noSync": "Ожидание следующего резервного копирования",
"webdav.syncError": "Ошибка резервного копирования", "webdav.syncError": "Ошибка резервного копирования",
"webdav.lastSync": "Последняя синхронизация" "webdav.lastSync": "Последняя синхронизация",
"notion.api_key":"Ключ API Notion",
"notion.database_id":"ID базы данных Notion",
"notion.title":"Настройки Notion"
}, },
"quickAssistant": { "quickAssistant": {
"title": "Быстрый помощник", "title": "Быстрый помощник",

View File

@ -116,6 +116,7 @@
"topics.edit.title": "编辑话题名", "topics.edit.title": "编辑话题名",
"topics.export.image": "导出为图片", "topics.export.image": "导出为图片",
"topics.export.md": "导出为 Markdown", "topics.export.md": "导出为 Markdown",
"topics.export.notion": "导出到 Notion",
"topics.export.title": "导出", "topics.export.title": "导出",
"topics.export.word": "导出为 Word", "topics.export.word": "导出为 Word",
"topics.list": "话题列表", "topics.list": "话题列表",
@ -297,7 +298,12 @@
"error.get_embedding_dimensions": "获取嵌入维度失败", "error.get_embedding_dimensions": "获取嵌入维度失败",
"group.delete.title": "删除分组消息", "group.delete.title": "删除分组消息",
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答", "group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
"mention.title": "切换模型回答" "mention.title": "切换模型回答",
"error.notion.export":"Notion 导入失败",
"error.notion.no_api_key":"未配置Notion ApiKey或Notion DatabaseID",
"success.notion.export":"导入Notion成功",
"warn.notion.exporting":"Notion正在导入请勿重复导入"
}, },
"minapp": { "minapp": {
"title": "小程序", "title": "小程序",
@ -420,7 +426,10 @@
"webdav.autoSync.off": "关闭", "webdav.autoSync.off": "关闭",
"webdav.noSync": "等待下次备份", "webdav.noSync": "等待下次备份",
"webdav.syncError": "备份错误", "webdav.syncError": "备份错误",
"webdav.lastSync": "上次备份时间" "webdav.lastSync": "上次备份时间",
"notion.api_key":"Notion 密钥",
"notion.database_id":"Notion 数据库ID",
"notion.title":"Notion 配置"
}, },
"quickAssistant": { "quickAssistant": {
"title": "快捷助手", "title": "快捷助手",

View File

@ -116,6 +116,7 @@
"topics.edit.title": "編輯名稱", "topics.edit.title": "編輯名稱",
"topics.export.image": "匯出為圖片", "topics.export.image": "匯出為圖片",
"topics.export.md": "匯出為 Markdown", "topics.export.md": "匯出為 Markdown",
"topics.export.notion": "匯出到 Notion",
"topics.export.title": "匯出", "topics.export.title": "匯出",
"topics.export.word": "導出為 Word", "topics.export.word": "導出為 Word",
"topics.list": "話題列表", "topics.list": "話題列表",
@ -296,7 +297,11 @@
"error.get_embedding_dimensions": "獲取嵌入維度失敗", "error.get_embedding_dimensions": "獲取嵌入維度失敗",
"group.delete.title": "刪除分組消息", "group.delete.title": "刪除分組消息",
"group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答", "group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答",
"mention.title": "切換模型回答" "mention.title": "切換模型回答",
"error.notion.export": "Notion 匯入失敗",
"error.notion.no_api_key": "未配置 Notion ApiKey 或 Notion DatabaseID",
"success.notion.export": "匯入 Notion 成功",
"warn.notion.exporting": "Notion 正在匯入,請勿重複匯入"
}, },
"minapp": { "minapp": {
"title": "小程序", "title": "小程序",
@ -419,7 +424,10 @@
"webdav.autoSync.off": "關閉", "webdav.autoSync.off": "關閉",
"webdav.noSync": "等待下次備份", "webdav.noSync": "等待下次備份",
"webdav.syncError": "備份錯誤", "webdav.syncError": "備份錯誤",
"webdav.lastSync": "上次同步時間" "webdav.lastSync": "上次同步時間",
"notion.api_key": "Notion 金鑰",
"notion.database_id": "Notion 資料庫 ID",
"notion.title": "Notion 配置"
}, },
"quickAssistant": { "quickAssistant": {
"title": "快捷助手", "title": "快捷助手",

View File

@ -18,7 +18,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import store from '@renderer/store' import store from '@renderer/store'
import { setGenerating } from '@renderer/store/runtime' import { setGenerating } from '@renderer/store/runtime'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { exportTopicAsMarkdown, topicToMarkdown } from '@renderer/utils/export' import { exportTopicAsMarkdown, exportTopicToNotion, topicToMarkdown } from '@renderer/utils/export'
import { Dropdown, MenuProps } from 'antd' import { Dropdown, MenuProps } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { findIndex } from 'lodash' import { findIndex } from 'lodash'
@ -133,6 +133,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
key: 'markdown', key: 'markdown',
onClick: () => exportTopicAsMarkdown(topic) onClick: () => exportTopicAsMarkdown(topic)
}, },
{ {
label: t('chat.topics.export.word'), label: t('chat.topics.export.word'),
key: 'word', key: 'word',
@ -140,7 +141,12 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const markdown = await topicToMarkdown(topic) const markdown = await topicToMarkdown(topic)
window.api.export.toWord(markdown, topic.name) window.api.export.toWord(markdown, topic.name)
} }
} },
{
label: t('chat.topics.export.notion'),
key: 'notion',
onClick: () => exportTopicToNotion(topic)
},
] ]
} }
] ]

View File

@ -2,15 +2,72 @@ import { FileSearchOutlined, FolderOpenOutlined, SaveOutlined } from '@ant-desig
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { backup, reset, restore } from '@renderer/services/BackupService' import { backup, reset, restore } from '@renderer/services/BackupService'
import { RootState, useAppDispatch } from '@renderer/store'
import { setNotionApiKey, setNotionDatabaseID } from '@renderer/store/settings'
import { AppInfo } from '@renderer/types' import { AppInfo } from '@renderer/types'
import { Button,Modal, Typography } from 'antd' import { Button,Modal, Typography } from 'antd'
import Input from 'antd/es/input/Input'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import WebDavSettings from './WebDavSettings' import WebDavSettings from './WebDavSettings'
// 新增的 NotionSettings 组件
const NotionSettings: FC = () => {
const { t } = useTranslation()
const { theme } = useTheme()
const dispatch = useAppDispatch()
// 这里可以添加 Notion 相关的状态和逻辑
// 例如:
const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey);
const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID);
const handleNotionTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionApiKey(e.target.value))
};
const handleNotionDatabaseIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setNotionDatabaseID(e.target.value))
};
return (
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.notion.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
<Input.Password
type="text"
value={notionApiKey || ''}
onChange={handleNotionTokenChange}
onBlur={handleNotionTokenChange}
style={{ width: 250 }}
/>
</HStack>
</SettingRow>
<SettingDivider /> {/* 添加分割线 */}
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.database_id')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
<Input
type="text"
value={notionDatabaseID || ''}
onChange={handleNotionDatabaseIdChange}
onBlur={handleNotionDatabaseIdChange}
style={{ width: 250 }}
/>
</HStack>
</SettingRow>
</SettingGroup>
)
}
const DataSettings: FC = () => { const DataSettings: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [appInfo, setAppInfo] = useState<AppInfo>() const [appInfo, setAppInfo] = useState<AppInfo>()
@ -79,6 +136,7 @@ const DataSettings: FC = () => {
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<WebDavSettings /> <WebDavSettings />
</SettingGroup> </SettingGroup>
<NotionSettings />
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.data.title')}</SettingTitle> <SettingTitle>{t('settings.data.data.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />
@ -107,6 +165,7 @@ const DataSettings: FC = () => {
</HStack> </HStack>
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
</SettingContainer> </SettingContainer>
) )
} }

View File

@ -25,6 +25,11 @@ export interface RuntimeState {
resourcesPath: string resourcesPath: string
update: UpdateState update: UpdateState
webdavSync: WebDAVSyncState webdavSync: WebDAVSyncState
export: ExportState
}
export interface ExportState {
isExporting: boolean
} }
const initialState: RuntimeState = { const initialState: RuntimeState = {
@ -45,6 +50,9 @@ const initialState: RuntimeState = {
lastSyncTime: null, lastSyncTime: null,
syncing: false, syncing: false,
lastSyncError: null lastSyncError: null
},
export: {
isExporting: false
} }
} }
@ -75,7 +83,11 @@ const runtimeSlice = createSlice({
}, },
setWebDAVSyncState: (state, action: PayloadAction<Partial<WebDAVSyncState>>) => { setWebDAVSyncState: (state, action: PayloadAction<Partial<WebDAVSyncState>>) => {
state.webdavSync = { ...state.webdavSync, ...action.payload } state.webdavSync = { ...state.webdavSync, ...action.payload }
} },
setExportState: (state, action: PayloadAction<Partial<ExportState>>) => {
state.export = { ...state.export, ...action.payload }
},
} }
}) })
@ -87,7 +99,8 @@ export const {
setFilesPath, setFilesPath,
setResourcesPath, setResourcesPath,
setUpdateState, setUpdateState,
setWebDAVSyncState setWebDAVSyncState,
setExportState
} = runtimeSlice.actions } = runtimeSlice.actions
export default runtimeSlice.reducer export default runtimeSlice.reducer

View File

@ -65,6 +65,8 @@ export interface SettingsState {
enableQuickAssistant: boolean enableQuickAssistant: boolean
clickTrayToShowQuickAssistant: boolean clickTrayToShowQuickAssistant: boolean
multiModelMessageStyle: MultiModelMessageStyle multiModelMessageStyle: MultiModelMessageStyle
notionDatabaseID: string | null
notionApiKey: string | null
} }
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold'
@ -115,7 +117,9 @@ const initialState: SettingsState = {
narrowMode: false, narrowMode: false,
enableQuickAssistant: false, enableQuickAssistant: false,
clickTrayToShowQuickAssistant: false, clickTrayToShowQuickAssistant: false,
multiModelMessageStyle: 'fold' multiModelMessageStyle: 'fold',
notionDatabaseID: '',
notionApiKey: ''
} }
const settingsSlice = createSlice({ const settingsSlice = createSlice({
@ -263,6 +267,12 @@ const settingsSlice = createSlice({
}, },
setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold'>) => { setMultiModelMessageStyle: (state, action: PayloadAction<'horizontal' | 'vertical' | 'fold'>) => {
state.multiModelMessageStyle = action.payload state.multiModelMessageStyle = action.payload
},
setNotionDatabaseID: (state, action: PayloadAction<string>) => {
state.notionDatabaseID = action.payload
},
setNotionApiKey: (state, action: PayloadAction<string>) => {
state.notionApiKey = action.payload
} }
} }
}) })
@ -312,7 +322,9 @@ export const {
setNarrowMode, setNarrowMode,
setClickTrayToShowQuickAssistant, setClickTrayToShowQuickAssistant,
setEnableQuickAssistant, setEnableQuickAssistant,
setMultiModelMessageStyle setMultiModelMessageStyle,
setNotionDatabaseID,
setNotionApiKey
} = settingsSlice.actions } = settingsSlice.actions
export default settingsSlice.reducer export default settingsSlice.reducer

View File

@ -1,4 +1,8 @@
import { Client } from '@notionhq/client'
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n'
import store from '@renderer/store'
import { setExportState } from '@renderer/store/runtime'
import { Message, Topic } from '@renderer/types' import { Message, Topic } from '@renderer/types'
export const messageToMarkdown = (message: Message) => { export const messageToMarkdown = (message: Message) => {
@ -29,3 +33,56 @@ export const exportTopicAsMarkdown = async (topic: Topic) => {
const markdown = await topicToMarkdown(topic) const markdown = await topicToMarkdown(topic)
window.api.file.save(fileName, markdown) window.api.file.save(fileName, markdown)
} }
export const exportTopicToNotion = async (topic: Topic) => {
const { isExporting } = store.getState().runtime.export
if (isExporting) {
window.message.warning({ content: i18n.t('message.warn.notion.exporting'), key: 'notion-exporting' })
return
}
setExportState({
isExporting: true
})
const { notionDatabaseID, notionApiKey } = store.getState().settings
if (!notionApiKey || !notionDatabaseID) {
window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' })
return
}
try {
const notion = new Client({ auth: notionApiKey });
const markdown = await topicToMarkdown(topic);
const requestBody = JSON.stringify({ md: markdown })
const res = await fetch('https://md2notion.hilars.dev', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: requestBody
});
const data = await res.json();
const notionBlocks = data;
const response = await notion.pages.create({
parent: { database_id: notionDatabaseID },
properties: {
Name: {
title: [{ text: { content: topic.name } }]
}
},
children: notionBlocks // 使用转换后的块
});
window.message.success({ content: i18n.t('message.success.notion.export'), key: 'notion-success' })
return response
} catch (error:any) {
window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-error' })
return null
} finally {
setExportState({
isExporting: false
})
}
};

View File

@ -1776,6 +1776,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@notionhq/client@npm:^2.2.15":
version: 2.2.15
resolution: "@notionhq/client@npm:2.2.15"
dependencies:
"@types/node-fetch": "npm:^2.5.10"
node-fetch: "npm:^2.6.1"
checksum: 10c0/4153c2e5b47d2ba141d025f2753d0e79ca9b9f25bd8bbdfa9dbf74fe4c2e157ea7964c59387d05163972c4575830bdc48d02db29270e244d81398df0f89fd7dd
languageName: node
linkType: hard
"@npmcli/agent@npm:^3.0.0": "@npmcli/agent@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "@npmcli/agent@npm:3.0.0" resolution: "@npmcli/agent@npm:3.0.0"
@ -2640,7 +2650,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node-fetch@npm:^2.6.4": "@types/node-fetch@npm:^2.5.10, @types/node-fetch@npm:^2.6.4":
version: 2.6.12 version: 2.6.12
resolution: "@types/node-fetch@npm:2.6.12" resolution: "@types/node-fetch@npm:2.6.12"
dependencies: dependencies:
@ -3011,6 +3021,7 @@ __metadata:
"@llm-tools/embedjs-loader-web": "npm:^0.1.25" "@llm-tools/embedjs-loader-web": "npm:^0.1.25"
"@llm-tools/embedjs-loader-xml": "npm:^0.1.25" "@llm-tools/embedjs-loader-xml": "npm:^0.1.25"
"@llm-tools/embedjs-openai": "npm:^0.1.25" "@llm-tools/embedjs-openai": "npm:^0.1.25"
"@notionhq/client": "npm:^2.2.15"
"@reduxjs/toolkit": "npm:^2.2.5" "@reduxjs/toolkit": "npm:^2.2.5"
"@types/adm-zip": "npm:^0" "@types/adm-zip": "npm:^0"
"@types/fs-extra": "npm:^11" "@types/fs-extra": "npm:^11"
@ -9845,7 +9856,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-fetch@npm:^2.6.7": "node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7":
version: 2.7.0 version: 2.7.0
resolution: "node-fetch@npm:2.7.0" resolution: "node-fetch@npm:2.7.0"
dependencies: dependencies: