forked from XiaoMo/ChatGPT-Next-Web
Merge pull request #1741 from Yidadaa/bugfix-0524
feat: share to ShareGPT
This commit is contained in:
commit
887f93181c
@ -38,7 +38,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
- [x] System Prompt: pin a user defined prompt as system prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||||
- [x] User Prompt: user can edit and save custom prompts to prompt list
|
- [x] User Prompt: user can edit and save custom prompts to prompt list
|
||||||
- [x] Prompt Template: create a new chat with pre-defined in-context prompts [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
- [x] Prompt Template: create a new chat with pre-defined in-context prompts [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
||||||
- [ ] Share as image, share to ShareGPT
|
- [x] Share as image, share to ShareGPT [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||||
- [ ] Desktop App with tauri
|
- [ ] Desktop App with tauri
|
||||||
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
|
- [ ] Self-host Model: support llama, alpaca, ChatGLM, BELLE etc.
|
||||||
- [ ] Plugins: support network search, calculator, any other apis etc. [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
- [ ] 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.
|
|||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
- 🚀 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/).
|
- 🚀 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/).
|
||||||
|
- 🚀 v2.7 let's share conversations as image, or share to ShareGPT!
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
|
|||||||
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
- [x] 为每个对话设置系统 Prompt [#138](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)
|
||||||
- [x] 允许用户自行编辑内置 Prompt 列表
|
- [x] 允许用户自行编辑内置 Prompt 列表
|
||||||
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
- [x] 预制角色:使用预制角色快速定制新对话 [#993](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/993)
|
||||||
- [ ] 分享为图片,分享到 ShareGPT
|
- [x] 分享为图片,分享到 ShareGPT 链接 [#1741](https://github.com/Yidadaa/ChatGPT-Next-Web/pull/1741)
|
||||||
- [ ] 使用 tauri 打包桌面应用
|
- [ ] 使用 tauri 打包桌面应用
|
||||||
- [ ] 支持自部署的大语言模型
|
- [ ] 支持自部署的大语言模型
|
||||||
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
|
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 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.
|
|||||||
|
|
||||||
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
- 🚀 v2.0 已经发布,现在你可以使用面具功能快速创建预制对话了! 了解更多: [ChatGPT 提示词高阶技能:零次、一次和少样本提示](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/138)。
|
||||||
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
|
||||||
|
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ACCESS_CODE_PREFIX } from "../constant";
|
import { ACCESS_CODE_PREFIX } from "../constant";
|
||||||
import { ModelConfig, ModelType, useAccessStore } from "../store";
|
import { ChatMessage, ModelConfig, ModelType, useAccessStore } from "../store";
|
||||||
import { ChatGPTApi } from "./platforms/openai";
|
import { ChatGPTApi } from "./platforms/openai";
|
||||||
|
|
||||||
export const ROLES = ["system", "user", "assistant"] as const;
|
export const ROLES = ["system", "user", "assistant"] as const;
|
||||||
@ -54,6 +54,41 @@ export class ClientApi {
|
|||||||
prompts() {}
|
prompts() {}
|
||||||
|
|
||||||
masks() {}
|
masks() {}
|
||||||
|
|
||||||
|
async share(messages: ChatMessage[], avatarUrl: string | null = null) {
|
||||||
|
const msgs = messages
|
||||||
|
.map((m) => ({
|
||||||
|
from: m.role === "user" ? "human" : "gpt",
|
||||||
|
value: m.content,
|
||||||
|
}))
|
||||||
|
.concat([
|
||||||
|
{
|
||||||
|
from: "human",
|
||||||
|
value:
|
||||||
|
"Share from [ChatGPT Next Web]: https://github.com/Yidadaa/ChatGPT-Next-Web",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用
|
||||||
|
// Please do not modify this message
|
||||||
|
|
||||||
|
console.log("[Share]", msgs);
|
||||||
|
const res = await fetch("/sharegpt", {
|
||||||
|
body: JSON.stringify({
|
||||||
|
avatarUrl,
|
||||||
|
items: msgs,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
const resJson = await res.json();
|
||||||
|
console.log("[Share]", resJson);
|
||||||
|
if (resJson.id) {
|
||||||
|
return `https://shareg.pt/${resJson.id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = new ClientApi();
|
export const api = new ClientApi();
|
||||||
|
@ -12,14 +12,17 @@ import ShareIcon from "../icons/share.svg";
|
|||||||
import BotIcon from "../icons/bot.png";
|
import BotIcon from "../icons/bot.png";
|
||||||
|
|
||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { MessageSelector, useMessageSelector } from "./message-selector";
|
import { MessageSelector, useMessageSelector } from "./message-selector";
|
||||||
import { Avatar } from "./emoji";
|
import { Avatar } from "./emoji";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import NextImage from "next/image";
|
import NextImage from "next/image";
|
||||||
|
|
||||||
import { toBlob, toPng } from "html-to-image";
|
import { toBlob, toJpeg, toPng } from "html-to-image";
|
||||||
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
import { DEFAULT_MASK_AVATAR } from "../store/mask";
|
||||||
|
import { api } from "../client/api";
|
||||||
|
import { prettyObject } from "../utils/format";
|
||||||
|
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";
|
||||||
|
|
||||||
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
loading: () => <LoadingIcon />,
|
loading: () => <LoadingIcon />,
|
||||||
@ -214,12 +217,87 @@ export function MessageExporter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RenderExport(props: {
|
||||||
|
messages: ChatMessage[];
|
||||||
|
onRender: (messages: ChatMessage[]) => void;
|
||||||
|
}) {
|
||||||
|
const domRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!domRef.current) return;
|
||||||
|
const dom = domRef.current;
|
||||||
|
const messages = Array.from(
|
||||||
|
dom.getElementsByClassName(EXPORT_MESSAGE_CLASS_NAME),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (messages.length !== props.messages.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderMsgs = messages.map((v) => {
|
||||||
|
const [_, role] = v.id.split(":");
|
||||||
|
return {
|
||||||
|
role: role as any,
|
||||||
|
content: v.innerHTML,
|
||||||
|
date: "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
props.onRender(renderMsgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={domRef}>
|
||||||
|
{props.messages.map((m, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
id={`${m.role}:${i}`}
|
||||||
|
className={EXPORT_MESSAGE_CLASS_NAME}
|
||||||
|
>
|
||||||
|
<Markdown content={m.content} defaultShow />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function PreviewActions(props: {
|
export function PreviewActions(props: {
|
||||||
download: () => void;
|
download: () => void;
|
||||||
copy: () => void;
|
copy: () => void;
|
||||||
showCopy?: boolean;
|
showCopy?: boolean;
|
||||||
|
messages?: ChatMessage[];
|
||||||
}) {
|
}) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [shouldExport, setShouldExport] = useState(false);
|
||||||
|
|
||||||
|
const onRenderMsgs = (msgs: ChatMessage[]) => {
|
||||||
|
setShouldExport(false);
|
||||||
|
|
||||||
|
api
|
||||||
|
.share(msgs)
|
||||||
|
.then((res) => {
|
||||||
|
if (!res) return;
|
||||||
|
copyToClipboard(res);
|
||||||
|
setTimeout(() => {
|
||||||
|
window.open(res, "_blank");
|
||||||
|
}, 800);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("[Share]", e);
|
||||||
|
showToast(prettyObject(e));
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const share = async () => {
|
||||||
|
if (props.messages?.length) {
|
||||||
|
setLoading(true);
|
||||||
|
setShouldExport(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className={styles["preview-actions"]}>
|
<div className={styles["preview-actions"]}>
|
||||||
{props.showCopy && (
|
{props.showCopy && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -241,10 +319,25 @@ export function PreviewActions(props: {
|
|||||||
text={Locale.Export.Share}
|
text={Locale.Export.Share}
|
||||||
bordered
|
bordered
|
||||||
shadow
|
shadow
|
||||||
icon={<ShareIcon />}
|
icon={loading ? <LoadingIcon /> : <ShareIcon />}
|
||||||
onClick={() => showToast(Locale.WIP)}
|
onClick={share}
|
||||||
></IconButton>
|
></IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
right: "200vw",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shouldExport && (
|
||||||
|
<RenderExport
|
||||||
|
messages={props.messages ?? []}
|
||||||
|
onRender={onRenderMsgs}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +416,12 @@ export function ImagePreviewer(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["image-previewer"]}>
|
<div className={styles["image-previewer"]}>
|
||||||
<PreviewActions copy={copy} download={download} showCopy={!isMobile} />
|
<PreviewActions
|
||||||
|
copy={copy}
|
||||||
|
download={download}
|
||||||
|
showCopy={!isMobile}
|
||||||
|
messages={props.messages}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className={`${styles["preview-body"]} ${styles["default-theme"]}`}
|
className={`${styles["preview-body"]} ${styles["default-theme"]}`}
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
@ -417,7 +515,11 @@ export function MarkdownPreviewer(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PreviewActions copy={copy} download={download} />
|
<PreviewActions
|
||||||
|
copy={copy}
|
||||||
|
download={download}
|
||||||
|
messages={props.messages}
|
||||||
|
/>
|
||||||
<div className="markdown-body">
|
<div className="markdown-body">
|
||||||
<pre className={styles["export-content"]}>{mdText}</pre>
|
<pre className={styles["export-content"]}>{mdText}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,4 +4,9 @@
|
|||||||
padding: 5px 15px 5px 10px;
|
padding: 5px 15px 5px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
max-width: 40%;
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
max-width: calc(100% - 50px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,8 @@ export function MessageSelector(props: {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [startIndex, endIndex]);
|
}, [startIndex, endIndex]);
|
||||||
|
|
||||||
|
const LATEST_COUNT = 4;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["message-selector"]}>
|
<div className={styles["message-selector"]}>
|
||||||
<div className={styles["message-filter"]}>
|
<div className={styles["message-filter"]}>
|
||||||
@ -155,7 +157,7 @@ export function MessageSelector(props: {
|
|||||||
props.updateSelection((selection) => {
|
props.updateSelection((selection) => {
|
||||||
selection.clear();
|
selection.clear();
|
||||||
messages
|
messages
|
||||||
.slice(messageCount - 10)
|
.slice(messageCount - LATEST_COUNT)
|
||||||
.forEach((m) => selection.add(m.id!));
|
.forEach((m) => selection.add(m.id!));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -42,3 +42,5 @@ export const ACCESS_CODE_PREFIX = "ak-";
|
|||||||
export const LAST_INPUT_KEY = "last-input";
|
export const LAST_INPUT_KEY = "last-input";
|
||||||
|
|
||||||
export const REQUEST_TIMEOUT_MS = 60000;
|
export const REQUEST_TIMEOUT_MS = 60000;
|
||||||
|
|
||||||
|
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
|
||||||
|
@ -58,7 +58,7 @@ const cn = {
|
|||||||
Select: {
|
Select: {
|
||||||
Search: "搜索消息",
|
Search: "搜索消息",
|
||||||
All: "选取全部",
|
All: "选取全部",
|
||||||
Latest: "最近十条",
|
Latest: "最近几条",
|
||||||
Clear: "清除选中",
|
Clear: "清除选中",
|
||||||
},
|
},
|
||||||
Memory: {
|
Memory: {
|
||||||
|
@ -11,6 +11,10 @@ const nextConfig = {
|
|||||||
source: "/google-fonts/:path*",
|
source: "/google-fonts/:path*",
|
||||||
destination: "https://fonts.googleapis.com/:path*",
|
destination: "https://fonts.googleapis.com/:path*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/sharegpt",
|
||||||
|
destination: "https://sharegpt.com/api/conversations",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const apiUrl = process.env.API_URL;
|
const apiUrl = process.env.API_URL;
|
||||||
|
Loading…
Reference in New Issue
Block a user