Skip to content

Commit

Permalink
Merge pull request ChatGPTNextWeb#1741 from Yidadaa/bugfix-0524
Browse files Browse the repository at this point in the history
feat: share to ShareGPT
  • Loading branch information
Yidadaa committed May 24, 2023
2 parents bb3f6ee + 9f4a80f commit 887f931
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 31 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
[Demo](https://chatgpt.nextweb.fun/) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)

[演示](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)

[![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)

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
Expand All @@ -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] 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)
- [ ] 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
- [ ] 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)
Expand All @@ -51,6 +51,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel.
## 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.7 let's share conversations as image, or share to ShareGPT!

## 主要功能

Expand All @@ -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 列表
- [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 打包桌面应用
- [ ] 支持自部署的大语言模型
- [ ] 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165)
Expand All @@ -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)
- 💡 想要更方便地随时随地使用本项目?可以试下这款桌面插件:https://github.com/mushan0x0/AI0x0.com
- 🚀 v2.7 现在可以将会话分享为图片了,也可以分享到 ShareGPT 的在线链接。

## Get Started

Expand Down
37 changes: 36 additions & 1 deletion app/client/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ACCESS_CODE_PREFIX } from "../constant";
import { ModelConfig, ModelType, useAccessStore } from "../store";
import { ChatMessage, ModelConfig, ModelType, useAccessStore } from "../store";
import { ChatGPTApi } from "./platforms/openai";

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

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();
Expand Down
152 changes: 127 additions & 25 deletions app/components/exporter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import ShareIcon from "../icons/share.svg";
import BotIcon from "../icons/bot.png";

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 { Avatar } from "./emoji";
import dynamic from "next/dynamic";
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 { api } from "../client/api";
import { prettyObject } from "../utils/format";
import { EXPORT_MESSAGE_CLASS_NAME } from "../constant";

const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />,
Expand Down Expand Up @@ -214,37 +217,127 @@ 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: {
download: () => void;
copy: () => void;
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 (
<div className={styles["preview-actions"]}>
{props.showCopy && (
<>
<div className={styles["preview-actions"]}>
{props.showCopy && (
<IconButton
text={Locale.Export.Copy}
bordered
shadow
icon={<CopyIcon />}
onClick={props.copy}
></IconButton>
)}
<IconButton
text={Locale.Export.Copy}
text={Locale.Export.Download}
bordered
shadow
icon={<CopyIcon />}
onClick={props.copy}
icon={<DownloadIcon />}
onClick={props.download}
></IconButton>
)}
<IconButton
text={Locale.Export.Download}
bordered
shadow
icon={<DownloadIcon />}
onClick={props.download}
></IconButton>
<IconButton
text={Locale.Export.Share}
bordered
shadow
icon={<ShareIcon />}
onClick={() => showToast(Locale.WIP)}
></IconButton>
</div>
<IconButton
text={Locale.Export.Share}
bordered
shadow
icon={loading ? <LoadingIcon /> : <ShareIcon />}
onClick={share}
></IconButton>
</div>
<div
style={{
position: "fixed",
right: "200vw",
pointerEvents: "none",
}}
>
{shouldExport && (
<RenderExport
messages={props.messages ?? []}
onRender={onRenderMsgs}
/>
)}
</div>
</>
);
}

Expand Down Expand Up @@ -323,7 +416,12 @@ export function ImagePreviewer(props: {

return (
<div className={styles["image-previewer"]}>
<PreviewActions copy={copy} download={download} showCopy={!isMobile} />
<PreviewActions
copy={copy}
download={download}
showCopy={!isMobile}
messages={props.messages}
/>
<div
className={`${styles["preview-body"]} ${styles["default-theme"]}`}
ref={previewRef}
Expand Down Expand Up @@ -417,7 +515,11 @@ export function MarkdownPreviewer(props: {

return (
<>
<PreviewActions copy={copy} download={download} />
<PreviewActions
copy={copy}
download={download}
messages={props.messages}
/>
<div className="markdown-body">
<pre className={styles["export-content"]}>{mdText}</pre>
</div>
Expand Down
5 changes: 5 additions & 0 deletions app/components/input-range.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@
padding: 5px 15px 5px 10px;
font-size: 12px;
display: flex;
max-width: 40%;

input[type="range"] {
max-width: calc(100% - 50px);
}
}
4 changes: 3 additions & 1 deletion app/components/message-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export function MessageSelector(props: {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [startIndex, endIndex]);

const LATEST_COUNT = 4;

return (
<div className={styles["message-selector"]}>
<div className={styles["message-filter"]}>
Expand Down Expand Up @@ -155,7 +157,7 @@ export function MessageSelector(props: {
props.updateSelection((selection) => {
selection.clear();
messages
.slice(messageCount - 10)
.slice(messageCount - LATEST_COUNT)
.forEach((m) => selection.add(m.id!));
})
}
Expand Down
2 changes: 2 additions & 0 deletions app/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ export const ACCESS_CODE_PREFIX = "ak-";
export const LAST_INPUT_KEY = "last-input";

export const REQUEST_TIMEOUT_MS = 60000;

export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
2 changes: 1 addition & 1 deletion app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const cn = {
Select: {
Search: "搜索消息",
All: "选取全部",
Latest: "最近十条",
Latest: "最近几条",
Clear: "清除选中",
},
Memory: {
Expand Down
4 changes: 4 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const nextConfig = {
source: "/google-fonts/:path*",
destination: "https://fonts.googleapis.com/:path*",
},
{
source: "/sharegpt",
destination: "https://sharegpt.com/api/conversations",
},
];

const apiUrl = process.env.API_URL;
Expand Down

0 comments on commit 887f931

Please sign in to comment.