Skip to content

Commit 887f931

Browse files
authored
Merge pull request #1741 from Yidadaa/bugfix-0524
feat: share to ShareGPT
2 parents bb3f6ee + 9f4a80f commit 887f931

File tree

8 files changed

+183
-31
lines changed

8 files changed

+183
-31
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
1212
[Demo](https://chatgpt.nextweb.fun/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
1313

1414
[演示](https://chatgpt.nextweb.fun/) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
15-
15+
1616
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web)
1717

1818
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
@@ -38,7 +38,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
3838
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
3939
- [x] User Prompt: user can edit and save custom prompts to prompt list
4040
- [x] Prompt Template: create a new chat with pre-defined in-context prompts [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
41-
- [ ] Share as image, share to ShareGPT
41+
- [x] Share as image, share to ShareGPT [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
4242
- [ ] Desktop App with tauri
4343
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
4444
- [ ] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
@@ -51,6 +51,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
5151
## What's New
5252

5353
- 🚀 v2.0 is released, now you can create prompt templates, turn your ideas into reality! Read this: [ChatGPT Prompt Engineering Tips: Zero, One and Few Shot Prompting](https://www.allabtai.com/prompt-engineering-tips-zero-one-and-few-shot-prompting/).
54+
- 🚀 v2.7 let's share conversations as image, or share to ShareGPT!
5455

5556
## 主要功能
5657

@@ -70,7 +71,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
7071
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
7172
- [x] 允许用户自行编辑内置 Prompt 列表
7273
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
73-
- [ ] 分享为图片,分享到 ShareGPT
74+
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
7475
- [ ] 使用 tauri 打包桌面应用
7576
- [ ] 支持自部署的大语言模型
7677
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
@@ -84,6 +85,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
8485

8586
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
8687
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
88+
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
8789

8890
## Get Started
8991

app/client/api.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ACCESS_CODE_PREFIX } from "../constant";
2-
import { ModelConfig, ModelType, useAccessStore } from "../store";
2+
import { ChatMessage, ModelConfig, ModelType, useAccessStore } from "../store";
33
import { ChatGPTApi } from "./platforms/openai";
44

55
export const ROLES = ["system", "user", "assistant"] as const;
@@ -54,6 +54,41 @@ export class ClientApi {
5454
prompts() {}
5555

5656
masks() {}
57+
58+
async share(messages: ChatMessage[], avatarUrl: string | null = null) {
59+
const msgs = messages
60+
.map((m) => ({
61+
from: m.role === "user" ? "human" : "gpt",
62+
value: m.content,
63+
}))
64+
.concat([
65+
{
66+
from: "human",
67+
value:
68+
"Share from [ChatGPT Next Web]: https://github.com/Yidadaa/ChatGPT-Next-Web",
69+
},
70+
]);
71+
// 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用
72+
// Please do not modify this message
73+
74+
console.log("[Share]", msgs);
75+
const res = await fetch("/sharegpt", {
76+
body: JSON.stringify({
77+
avatarUrl,
78+
items: msgs,
79+
}),
80+
headers: {
81+
"Content-Type": "application/json",
82+
},
83+
method: "POST",
84+
});
85+
86+
const resJson = await res.json();
87+
console.log("[Share]", resJson);
88+
if (resJson.id) {
89+
return `https://shareg.pt/${resJson.id}`;
90+
}
91+
}
5792
}
5893

5994
export const api = new ClientApi();

app/components/exporter.tsx

Lines changed: 127 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ import ShareIcon from "../icons/share.svg";
1212
import BotIcon from "../icons/bot.png";
1313

1414
import DownloadIcon from "../icons/download.svg";
15-
import { useMemo, useRef, useState } from "react";
15+
import { useEffect, useMemo, useRef, useState } from "react";
1616
import { MessageSelector, useMessageSelector } from "./message-selector";
1717
import { Avatar } from "./emoji";
1818
import dynamic from "next/dynamic";
1919
import NextImage from "next/image";
2020

21-
import { toBlob, toPng } from "html-to-image";
21+
import { toBlob, toJpeg, toPng } from "html-to-image";
2222
import { DEFAULT_MASK_AVATAR } from "../store/mask";
23+
import { api } from "../client/api";
24+
import { prettyObject } from "../utils/format";
25+
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
2326

2427
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
2528
loading: () => <LoadingIcon />,
@@ -214,37 +217,127 @@ export function MessageExporter() {
214217
);
215218
}
216219

220+
export function RenderExport(props: {
221+
messages: ChatMessage[];
222+
onRender: (messages: ChatMessage[]) => void;
223+
}) {
224+
const domRef = useRef<HTMLDivElement>(null);
225+
226+
useEffect(() => {
227+
if (!domRef.current) return;
228+
const dom = domRef.current;
229+
const messages = Array.from(
230+
dom.getElementsByClassName(EXPORT_MESSAGE_CLASS_NAME),
231+
);
232+
233+
if (messages.length !== props.messages.length) {
234+
return;
235+
}
236+
237+
const renderMsgs = messages.map((v) => {
238+
const [_, role] = v.id.split(":");
239+
return {
240+
role: role as any,
241+
content: v.innerHTML,
242+
date: "",
243+
};
244+
});
245+
246+
props.onRender(renderMsgs);
247+
});
248+
249+
return (
250+
<div ref={domRef}>
251+
{props.messages.map((m, i) => (
252+
<div
253+
key={i}
254+
id={`${m.role}:${i}`}
255+
className={EXPORT_MESSAGE_CLASS_NAME}
256+
>
257+
<Markdown content={m.content} defaultShow />
258+
</div>
259+
))}
260+
</div>
261+
);
262+
}
263+
217264
export function PreviewActions(props: {
218265
download: () => void;
219266
copy: () => void;
220267
showCopy?: boolean;
268+
messages?: ChatMessage[];
221269
}) {
270+
const [loading, setLoading] = useState(false);
271+
const [shouldExport, setShouldExport] = useState(false);
272+
273+
const onRenderMsgs = (msgs: ChatMessage[]) => {
274+
setShouldExport(false);
275+
276+
api
277+
.share(msgs)
278+
.then((res) => {
279+
if (!res) return;
280+
copyToClipboard(res);
281+
setTimeout(() => {
282+
window.open(res, "_blank");
283+
}, 800);
284+
})
285+
.catch((e) => {
286+
console.error("[Share]", e);
287+
showToast(prettyObject(e));
288+
})
289+
.finally(() => setLoading(false));
290+
};
291+
292+
const share = async () => {
293+
if (props.messages?.length) {
294+
setLoading(true);
295+
setShouldExport(true);
296+
}
297+
};
298+
222299
return (
223-
<div className={styles["preview-actions"]}>
224-
{props.showCopy && (
300+
<>
301+
<div className={styles["preview-actions"]}>
302+
{props.showCopy && (
303+
<IconButton
304+
text={Locale.Export.Copy}
305+
bordered
306+
shadow
307+
icon={<CopyIcon />}
308+
onClick={props.copy}
309+
></IconButton>
310+
)}
225311
<IconButton
226-
text={Locale.Export.Copy}
312+
text={Locale.Export.Download}
227313
bordered
228314
shadow
229-
icon={<CopyIcon />}
230-
onClick={props.copy}
315+
icon={<DownloadIcon />}
316+
onClick={props.download}
231317
></IconButton>
232-
)}
233-
<IconButton
234-
text={Locale.Export.Download}
235-
bordered
236-
shadow
237-
icon={<DownloadIcon />}
238-
onClick={props.download}
239-
></IconButton>
240-
<IconButton
241-
text={Locale.Export.Share}
242-
bordered
243-
shadow
244-
icon={<ShareIcon />}
245-
onClick={() => showToast(Locale.WIP)}
246-
></IconButton>
247-
</div>
318+
<IconButton
319+
text={Locale.Export.Share}
320+
bordered
321+
shadow
322+
icon={loading ? <LoadingIcon /> : <ShareIcon />}
323+
onClick={share}
324+
></IconButton>
325+
</div>
326+
<div
327+
style={{
328+
position: "fixed",
329+
right: "200vw",
330+
pointerEvents: "none",
331+
}}
332+
>
333+
{shouldExport && (
334+
<RenderExport
335+
messages={props.messages ?? []}
336+
onRender={onRenderMsgs}
337+
/>
338+
)}
339+
</div>
340+
</>
248341
);
249342
}
250343

@@ -323,7 +416,12 @@ export function ImagePreviewer(props: {
323416

324417
return (
325418
<div className={styles["image-previewer"]}>
326-
<PreviewActions copy={copy} download={download} showCopy={!isMobile} />
419+
<PreviewActions
420+
copy={copy}
421+
download={download}
422+
showCopy={!isMobile}
423+
messages={props.messages}
424+
/>
327425
<div
328426
className={`${styles["preview-body"]} ${styles["default-theme"]}`}
329427
ref={previewRef}
@@ -417,7 +515,11 @@ export function MarkdownPreviewer(props: {
417515

418516
return (
419517
<>
420-
<PreviewActions copy={copy} download={download} />
518+
<PreviewActions
519+
copy={copy}
520+
download={download}
521+
messages={props.messages}
522+
/>
421523
<div className="markdown-body">
422524
<pre className={styles["export-content"]}>{mdText}</pre>
423525
</div>

app/components/input-range.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@
44
padding: 5px 15px 5px 10px;
55
font-size: 12px;
66
display: flex;
7+
max-width: 40%;
8+
9+
input[type="range"] {
10+
max-width: calc(100% - 50px);
11+
}
712
}

app/components/message-selector.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export function MessageSelector(props: {
126126
// eslint-disable-next-line react-hooks/exhaustive-deps
127127
}, [startIndex, endIndex]);
128128

129+
const LATEST_COUNT = 4;
130+
129131
return (
130132
<div className={styles["message-selector"]}>
131133
<div className={styles["message-filter"]}>
@@ -155,7 +157,7 @@ export function MessageSelector(props: {
155157
props.updateSelection((selection) => {
156158
selection.clear();
157159
messages
158-
.slice(messageCount - 10)
160+
.slice(messageCount - LATEST_COUNT)
159161
.forEach((m) => selection.add(m.id!));
160162
})
161163
}

app/constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ export const ACCESS_CODE_PREFIX = "ak-";
4242
export const LAST_INPUT_KEY = "last-input";
4343

4444
export const REQUEST_TIMEOUT_MS = 60000;
45+
46+
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";

app/locales/cn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const cn = {
5858
Select: {
5959
Search: "搜索消息",
6060
All: "选取全部",
61-
Latest: "最近十条",
61+
Latest: "最近几条",
6262
Clear: "清除选中",
6363
},
6464
Memory: {

next.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ const nextConfig = {
1111
source: "/google-fonts/:path*",
1212
destination: "https://fonts.googleapis.com/:path*",
1313
},
14+
{
15+
source: "/sharegpt",
16+
destination: "https://sharegpt.com/api/conversations",
17+
},
1418
];
1519

1620
const apiUrl = process.env.API_URL;

0 commit comments

Comments
 (0)