From 3e65ef3beaa6550763feb1f3bcb9d4e59c2f0a07 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 25 May 2023 01:04:37 +0800 Subject: [PATCH] feat: share to ShareGPT --- app/client/api.ts | 37 ++++++- app/components/exporter.tsx | 152 +++++++++++++++++++++++----- app/components/message-selector.tsx | 4 +- app/constant.ts | 2 + app/locales/cn.ts | 2 +- next.config.mjs | 4 + 6 files changed, 173 insertions(+), 28 deletions(-) diff --git a/app/client/api.ts b/app/client/api.ts index a966d733..fb829f97 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -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; @@ -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(); diff --git a/app/components/exporter.tsx b/app/components/exporter.tsx index a9a1071d..10d5af99 100644 --- a/app/components/exporter.tsx +++ b/app/components/exporter.tsx @@ -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: () => , @@ -214,37 +217,127 @@ export function MessageExporter() { ); } +export function RenderExport(props: { + messages: ChatMessage[]; + onRender: (messages: ChatMessage[]) => void; +}) { + const domRef = useRef(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 ( +
+ {props.messages.map((m, i) => ( +
+ +
+ ))} +
+ ); +} + 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 ( -
- {props.showCopy && ( + <> +
+ {props.showCopy && ( + } + onClick={props.copy} + > + )} } - onClick={props.copy} + icon={} + onClick={props.download} > - )} - } - onClick={props.download} - > - } - onClick={() => showToast(Locale.WIP)} - > -
+ : } + onClick={share} + > +
+
+ {shouldExport && ( + + )} +
+ ); } @@ -323,7 +416,12 @@ export function ImagePreviewer(props: { return (
- +
- +
{mdText}
diff --git a/app/components/message-selector.tsx b/app/components/message-selector.tsx index 837591ac..300d4537 100644 --- a/app/components/message-selector.tsx +++ b/app/components/message-selector.tsx @@ -126,6 +126,8 @@ export function MessageSelector(props: { // eslint-disable-next-line react-hooks/exhaustive-deps }, [startIndex, endIndex]); + const LATEST_COUNT = 4; + return (
@@ -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!)); }) } diff --git a/app/constant.ts b/app/constant.ts index 577c0af6..0fb18c2f 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -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"; diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 989a54bf..48134e38 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -58,7 +58,7 @@ const cn = { Select: { Search: "搜索消息", All: "选取全部", - Latest: "最近十条", + Latest: "最近几条", Clear: "清除选中", }, Memory: { diff --git a/next.config.mjs b/next.config.mjs index 9c0ce9fa..34c058b7 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -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;