mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-11 20:43:42 +08:00
43
app/api/notion/route.ts
Normal file
43
app/api/notion/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Client } from "@notionhq/client";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { markdownToBlocks } from "@tryfabric/martian";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const body = await req.text();
|
||||
const { notionIntegrate, database_id, topic, mdText } = JSON.parse(body);
|
||||
|
||||
if (!(notionIntegrate && database_id && topic && mdText))
|
||||
return NextResponse.json({
|
||||
error:
|
||||
"Request body must contains notionIntegrateToken and notionDatabaseID",
|
||||
});
|
||||
|
||||
const notionService = new Client({
|
||||
auth: notionIntegrate,
|
||||
});
|
||||
|
||||
const blocks = markdownToBlocks(mdText);
|
||||
console.log(blocks);
|
||||
|
||||
const res = await notionService.pages.create({
|
||||
parent: {
|
||||
database_id,
|
||||
},
|
||||
properties: {
|
||||
Name: {
|
||||
title: [
|
||||
{
|
||||
type: "text",
|
||||
text: {
|
||||
content: topic,
|
||||
link: null,
|
||||
},
|
||||
plain_text: topic,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
children: blocks,
|
||||
});
|
||||
return NextResponse.json(res);
|
||||
}
|
@@ -14,6 +14,7 @@ import MaskIcon from "../icons/mask.svg";
|
||||
import MaxIcon from "../icons/max.svg";
|
||||
import MinIcon from "../icons/min.svg";
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
import NotionIcon from "../icons/notion.svg";
|
||||
|
||||
import LightIcon from "../icons/light.svg";
|
||||
import DarkIcon from "../icons/dark.svg";
|
||||
@@ -109,6 +110,97 @@ function exportMessages(messages: Message[], topic: string) {
|
||||
});
|
||||
}
|
||||
|
||||
const SaveToNotionFC = ({
|
||||
mdText,
|
||||
topic,
|
||||
}: Record<"mdText" | "topic", string>) => {
|
||||
const [topicValue, setTopicValue] = useState(topic),
|
||||
mdTextRef = useRef(mdText);
|
||||
|
||||
const handleTopicValueChange = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setTopicValue(e.currentTarget.value),
|
||||
handleMdTextChange = (e: React.FormEvent<HTMLPreElement>) => {
|
||||
mdTextRef.current = e.currentTarget.innerText;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem title="Title:">
|
||||
<input
|
||||
className={styles["chat-input"]}
|
||||
style={{
|
||||
marginLeft: "1rem",
|
||||
}}
|
||||
value={topicValue}
|
||||
onChange={handleTopicValueChange}
|
||||
/>
|
||||
</ListItem>
|
||||
<div className="markdown-body">
|
||||
<pre
|
||||
contentEditable
|
||||
className={styles["export-content"]}
|
||||
onInput={handleMdTextChange}
|
||||
>
|
||||
{mdText}
|
||||
</pre>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function saveMessagesToNotion(
|
||||
messages: Message[],
|
||||
topic: string,
|
||||
notionIntegrate: string,
|
||||
database_id: string,
|
||||
) {
|
||||
const mdText =
|
||||
`# ${topic}\n\n` +
|
||||
messages
|
||||
.map((m) => {
|
||||
return m.role === "user"
|
||||
? `## ${Locale.SaveToNotion.MessageFromYou}:\n${m.content}`
|
||||
: `## ${
|
||||
Locale.SaveToNotion.MessageFromChatGPT
|
||||
}:\n${m.content.trim()}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const handleSaveToNotion = async () => {
|
||||
// TODO: editable topic, mdText and tags
|
||||
fetch("/api/notion", {
|
||||
method: "post",
|
||||
body: JSON.stringify({
|
||||
notionIntegrate,
|
||||
database_id,
|
||||
topic,
|
||||
mdText,
|
||||
}),
|
||||
}).then((res) => res.json());
|
||||
};
|
||||
|
||||
showModal({
|
||||
title: Locale.SaveToNotion.Title,
|
||||
children: <SaveToNotionFC mdText={mdText} topic={topic} />,
|
||||
actions: [
|
||||
<IconButton
|
||||
key="copy"
|
||||
icon={<CopyIcon />}
|
||||
bordered
|
||||
text={Locale.SaveToNotion.Copy}
|
||||
onClick={() => copyToClipboard(mdText)}
|
||||
/>,
|
||||
<IconButton
|
||||
key="download"
|
||||
icon={<NotionIcon />}
|
||||
bordered
|
||||
text={Locale.SaveToNotion.Save}
|
||||
onClick={handleSaveToNotion}
|
||||
/>,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function SessionConfigModel(props: { onClose: () => void }) {
|
||||
const chatStore = useChatStore();
|
||||
const session = chatStore.currentSession();
|
||||
@@ -619,6 +711,21 @@ export function Chat() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="window-action-button">
|
||||
<IconButton
|
||||
icon={<NotionIcon />}
|
||||
bordered
|
||||
title={Locale.Chat.Actions.SaveToNotion}
|
||||
onClick={() => {
|
||||
saveMessagesToNotion(
|
||||
session.messages.filter((msg) => !msg.isError),
|
||||
session.topic,
|
||||
config.notionInegration,
|
||||
config.notionDatabaseID,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!isMobileScreen && (
|
||||
<div className="window-action-button">
|
||||
<IconButton
|
||||
|
@@ -451,6 +451,35 @@ export function Settings() {
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
title="Save To Notion"
|
||||
subTitle="Your notion integration token"
|
||||
>
|
||||
<PasswordInput
|
||||
value={config.notionInegration}
|
||||
type="text"
|
||||
placeholder="Integration Token"
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) => (config.notionInegration = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem title="Save To Notion" subTitle="Your notion database id">
|
||||
<PasswordInput
|
||||
value={config.notionDatabaseID}
|
||||
type="text"
|
||||
placeholder="Notion Database ID"
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) => (config.notionDatabaseID = e.currentTarget.value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<List>
|
||||
|
1
app/icons/notion.svg
Normal file
1
app/icons/notion.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="800px" height="800px" viewBox="0 0 192 192" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#000000" fill-rule="evenodd" d="m138.462 21.522 27.784 19.588.044.033.275.201c1.713 1.256 3.349 2.455 4.452 3.83 1.411 1.76 1.884 3.644 1.884 5.877v104.706c0 3.587-.635 7.178-3.058 9.934-2.451 2.789-6.145 4.067-10.732 4.394l-.018.001-98.629 5.971-.021.001c-3.242.154-6.094.035-8.669-.907-2.688-.984-4.719-2.727-6.604-5.129l-.01-.012-19.979-25.979-.012-.017c-3.81-5.086-5.723-9.348-5.723-14.509V34.509c0-3.12.688-6.394 2.745-9.033 2.124-2.727 5.356-4.328 9.503-4.686l.058-.005 84.854-4.344c5.192-.445 8.938-.576 12.286.185 3.459.787 6.208 2.452 9.57 4.896ZM56.43 157.336h.002v3.3c0 1.904.47 2.337.613 2.452.296.235 1.203.652 3.642.518l97.449-5.371c1.928-.106 2.256-.649 2.348-.801l.005-.008c.29-.476.486-1.407.486-3.357V60.001c0-1.635-.334-2.218-.421-2.327l-.005-.007-.002-.003-.006-.002a.117.117 0 0 1-.012-.004c-.053-.019-.263-.078-.724-.037l-.057.005-101.622 5.668c-.624.056-.973.163-1.152.242-.142.062-.173.104-.181.116l-.002.002c-.066.085-.36.586-.36 2.321v91.361Zm9.085-106.705 87.074-4.506-21.028-15.375-.039-.031c-1.259-.98-2.507-1.854-4.12-2.46-1.588-.597-3.695-.993-6.669-.734l-.05.005-87.009 4.898h-.01a6.453 6.453 0 0 0-.893.116L49.934 48.56c2.037 1.646 3.109 2.146 4.337 2.367 1.538.277 3.52.167 7.722-.115l3.522-.237v.056Zm-34.231-3.586v83.893c0 .538.175 1.061.498 1.49l13.174 17.464V61.224a2.47 2.47 0 0 0-.877-1.889l-.08-.068-12.715-12.222Zm109.871 35.062c.451 2.04 0 4.082-2.041 4.315l-3.393.673v49.881c-2.947 1.586-5.66 2.492-7.927 2.492-3.622 0-4.528-1.134-7.239-4.53l-.003-.003L98.36 100.02v33.78l7.02 1.59s0 4.082-5.664 4.082l-15.615.906c-.455-.91 0-3.176 1.582-3.627l4.078-1.131V90.955l-5.66-.459c-.454-2.04.677-4.987 3.85-5.216l16.754-1.128 23.09 35.367V88.231l-5.885-.677c-.455-2.499 1.356-4.315 3.618-4.536l15.627-.91v-.001Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
@@ -15,6 +15,7 @@ const cn = {
|
||||
ChatList: "查看消息列表",
|
||||
CompressedHistory: "查看压缩后的历史 Prompt",
|
||||
Export: "导出聊天记录",
|
||||
SaveToNotion: "保存聊天记录到Notion",
|
||||
Copy: "复制",
|
||||
Stop: "停止",
|
||||
Retry: "重试",
|
||||
@@ -42,6 +43,13 @@ const cn = {
|
||||
MessageFromYou: "来自你的消息",
|
||||
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "保存聊天记录到Notion",
|
||||
Copy: "全部复制",
|
||||
Save: "保存",
|
||||
MessageFromYou: "来自你的消息",
|
||||
MessageFromChatGPT: "来自 ChatGPT 的消息",
|
||||
},
|
||||
Memory: {
|
||||
Title: "历史摘要",
|
||||
EmptyContent: "对话内容过短,无需总结",
|
||||
|
@@ -16,6 +16,7 @@ const de: LocaleType = {
|
||||
ChatList: "Zur Chat-Liste gehen",
|
||||
CompressedHistory: "Komprimierter Gedächtnis-Prompt",
|
||||
Export: "Alle Nachrichten als Markdown exportieren",
|
||||
SaveToNotion: "Alle Nachrichten in Notion speichern",
|
||||
Copy: "Kopieren",
|
||||
Stop: "Stop",
|
||||
Retry: "Wiederholen",
|
||||
@@ -43,6 +44,13 @@ const de: LocaleType = {
|
||||
MessageFromYou: "Deine Nachricht",
|
||||
MessageFromChatGPT: "Nachricht von ChatGPT",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "Alle Nachrichten in Notion speichern",
|
||||
Copy: "Alles kopieren",
|
||||
Save: "Speichern",
|
||||
MessageFromYou: "Deine Nachricht",
|
||||
MessageFromChatGPT: "Nachricht von ChatGPT",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Gedächtnis-Prompt",
|
||||
EmptyContent: "Noch nichts.",
|
||||
|
@@ -16,6 +16,7 @@ const en: LocaleType = {
|
||||
ChatList: "Go To Chat List",
|
||||
CompressedHistory: "Compressed History Memory Prompt",
|
||||
Export: "Export All Messages as Markdown",
|
||||
SaveToNotion: "Save All Messages to notion",
|
||||
Copy: "Copy",
|
||||
Stop: "Stop",
|
||||
Retry: "Retry",
|
||||
@@ -43,6 +44,13 @@ const en: LocaleType = {
|
||||
MessageFromYou: "Message From You",
|
||||
MessageFromChatGPT: "Message From ChatGPT",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "Save All Messages To Notion",
|
||||
Copy: "Copy All",
|
||||
Save: "Save",
|
||||
MessageFromYou: "Message From You",
|
||||
MessageFromChatGPT: "Message From ChatGPT",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Memory Prompt",
|
||||
EmptyContent: "Nothing yet.",
|
||||
|
@@ -16,6 +16,7 @@ const es: LocaleType = {
|
||||
ChatList: "Ir a la lista de chats",
|
||||
CompressedHistory: "Historial de memoria comprimido",
|
||||
Export: "Exportar todos los mensajes como Markdown",
|
||||
SaveToNotion: "Guardar todos los mensajes en Notion",
|
||||
Copy: "Copiar",
|
||||
Stop: "Detener",
|
||||
Retry: "Reintentar",
|
||||
@@ -43,6 +44,13 @@ const es: LocaleType = {
|
||||
MessageFromYou: "Mensaje de ti",
|
||||
MessageFromChatGPT: "Mensaje de ChatGPT",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "Guardar todos los mensajes en Notion",
|
||||
Copy: "Copiar todo",
|
||||
Save: "Guardar",
|
||||
MessageFromYou: "Mensaje de ti",
|
||||
MessageFromChatGPT: "Mensaje de ChatGPT",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Historial de memoria",
|
||||
EmptyContent: "Aún no hay nada.",
|
||||
|
@@ -16,6 +16,7 @@ const it: LocaleType = {
|
||||
ChatList: "Vai alla Chat List",
|
||||
CompressedHistory: "Prompt di memoria della cronologia compressa",
|
||||
Export: "Esportazione di tutti i messaggi come Markdown",
|
||||
SaveToNotion: "Salva tutti i messaggi su Notion",
|
||||
Copy: "Copia",
|
||||
Stop: "Stop",
|
||||
Retry: "Riprova",
|
||||
@@ -43,6 +44,13 @@ const it: LocaleType = {
|
||||
MessageFromYou: "Messaggio da te",
|
||||
MessageFromChatGPT: "Messaggio da ChatGPT",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "Salva tutti i messaggi su Notion",
|
||||
Copy: "Copia tutto",
|
||||
Save: "Salva",
|
||||
MessageFromYou: "Messaggio da te",
|
||||
MessageFromChatGPT: "Messaggio da ChatGPT",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Prompt di memoria",
|
||||
EmptyContent: "Vuoto.",
|
||||
|
@@ -16,6 +16,7 @@ const jp: LocaleType = {
|
||||
ChatList: "メッセージリストを表示",
|
||||
CompressedHistory: "圧縮された履歴プロンプトを表示",
|
||||
Export: "チャット履歴をエクスポート",
|
||||
SaveToNotion: "すべてのメッセージをNotionに保存する",
|
||||
Copy: "コピー",
|
||||
Stop: "停止",
|
||||
Retry: "リトライ",
|
||||
@@ -43,6 +44,13 @@ const jp: LocaleType = {
|
||||
MessageFromYou: "あなたからのメッセージ",
|
||||
MessageFromChatGPT: "ChatGPTからのメッセージ",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "すべてのメッセージをNotionに保存する",
|
||||
Copy: "すべてコピー",
|
||||
Save: "保存する",
|
||||
MessageFromYou: "あなたからのメッセージ",
|
||||
MessageFromChatGPT: "ChatGPTからのメッセージ",
|
||||
},
|
||||
Memory: {
|
||||
Title: "履歴メモリ",
|
||||
EmptyContent: "まだ記憶されていません",
|
||||
|
@@ -16,6 +16,7 @@ const tr: LocaleType = {
|
||||
ChatList: "Sohbet Listesine Git",
|
||||
CompressedHistory: "Sıkıştırılmış Geçmiş Bellek Komutu",
|
||||
Export: "Tüm Mesajları Markdown Olarak Dışa Aktar",
|
||||
SaveToNotion: "Tüm Mesajları Notion'a Kaydet",
|
||||
Copy: "Kopyala",
|
||||
Stop: "Durdur",
|
||||
Retry: "Tekrar Dene",
|
||||
@@ -43,6 +44,13 @@ const tr: LocaleType = {
|
||||
MessageFromYou: "Sizin Mesajınız",
|
||||
MessageFromChatGPT: "ChatGPT'nin Mesajı",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "Tüm Mesajları Notion'a Kaydet",
|
||||
Copy: "Tümünü Kopyala",
|
||||
Save: "Kaydet",
|
||||
MessageFromYou: "Sizin Mesajınız",
|
||||
MessageFromChatGPT: "ChatGPT'nin Mesajı",
|
||||
},
|
||||
Memory: {
|
||||
Title: "Bellek Komutları",
|
||||
EmptyContent: "Henüz değil.",
|
||||
|
@@ -15,6 +15,7 @@ const tw: LocaleType = {
|
||||
ChatList: "查看訊息列表",
|
||||
CompressedHistory: "查看壓縮後的歷史 Prompt",
|
||||
Export: "匯出聊天紀錄",
|
||||
SaveToNotion: "將聊天記錄保存到notion",
|
||||
Copy: "複製",
|
||||
Stop: "停止",
|
||||
Retry: "重試",
|
||||
@@ -42,6 +43,13 @@ const tw: LocaleType = {
|
||||
MessageFromYou: "來自您的訊息",
|
||||
MessageFromChatGPT: "來自 ChatGPT 的訊息",
|
||||
},
|
||||
SaveToNotion: {
|
||||
Title: "將聊天記錄保存到Notion",
|
||||
Copy: "複製全部",
|
||||
Save: "保存",
|
||||
MessageFromYou: "來自您的訊息",
|
||||
MessageFromChatGPT: "來自 ChatGPT 的訊息",
|
||||
},
|
||||
Memory: {
|
||||
Title: "上下文記憶 Prompt",
|
||||
EmptyContent: "尚未記憶",
|
||||
|
@@ -26,6 +26,9 @@ export const DEFAULT_CONFIG = {
|
||||
sendPreviewBubble: true,
|
||||
sidebarWidth: 300,
|
||||
|
||||
notionInegration: "",
|
||||
notionDatabaseID: "",
|
||||
|
||||
disablePromptHint: false,
|
||||
|
||||
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
|
||||
|
@@ -29,7 +29,6 @@ export function middleware(req: NextRequest) {
|
||||
console.log("[Auth] hashed access code:", hashedCode);
|
||||
console.log("[User IP] ", getIP(req));
|
||||
console.log("[Time] ", new Date().toLocaleString());
|
||||
|
||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hello-pangea/dnd": "^16.2.0",
|
||||
"@notionhq/client": "^2.2.4",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"emoji-picker-react": "^4.4.7",
|
||||
"eventsource-parser": "^0.1.0",
|
||||
|
Reference in New Issue
Block a user