update sharing page

This commit is contained in:
Zhang Minghan 2023-10-18 23:29:17 +08:00
parent 8e02dae8d3
commit 0f345fbfa4
10 changed files with 307 additions and 20 deletions

133
app/src/assets/sharing.less Normal file
View File

@ -0,0 +1,133 @@
.sharing-page {
position: relative;
display: flex;
width: 100%;
height: calc(100vh - 56px);
padding: 0 2rem;
}
.loading {
margin: auto;
transform: translateY(-28px);
user-select: none;
.loader {
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
animation: spin 1.25s cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
}
.error-container {
display: flex;
flex-direction: column;
margin: auto;
transform: translateY(-28px);
color: hsl(var(--text));
text-align: center;
align-items: center;
user-select: none;
.title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.75rem;
}
.message {
font-size: 1rem;
}
}
.sharing-container {
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
margin: auto;
padding: 0;
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
width: 80vw;
height: 70vh;
.header {
display: flex;
flex-direction: row;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--background-container));
color: hsl(var(--text));
padding: 0.85rem 1rem 0.75rem;
align-items: center;
.user {
display: flex;
flex-direction: row;
align-items: center;
user-select: none;
flex-shrink: 0;
img {
width: 2rem;
height: 2rem;
border-radius: 4px;
margin-right: 0.75rem;
flex-shrink: 0;
}
span {
font-size: 1rem;
transform: translateY(-2px);
white-space: nowrap;
@media (max-width: 768px) {
display: none;
}
}
}
.name {
margin: 0 auto;
padding: 0 1rem;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time {
user-select: none;
white-space: nowrap;
flex-shrink: 0;
}
}
.body {
display: flex;
flex-direction: column;
flex-grow: 1;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
touch-action: pan-y;
padding: 1rem;
scrollbar-width: thin;
gap: 8px;
}
.action {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
padding: 0.75rem 1.5rem 1rem;
button {
white-space: nowrap;
}
}
}

View File

@ -1,6 +1,6 @@
import axios from "axios";
export const version = "3.4.1";
export const version = "3.4.2";
export const deploy: boolean = false;
export let rest_api: string = "http://localhost:8094";
export let ws_api: string = "ws://localhost:8094";

View File

@ -4,12 +4,6 @@ import { setHistory } from "../store/chat.ts";
import { manager } from "./manager.ts";
import { AppDispatch } from "../store";
type SharingForm = {
status: boolean;
message: string;
data: string;
}
export async function updateConversationList(
dispatch: AppDispatch,
): Promise<void> {
@ -42,17 +36,6 @@ export async function deleteConversation(
return true;
}
export async function shareConversation(
id: number, refs: number[] = [-1],
): Promise<SharingForm> {
try {
const resp = await axios.post("/conversation/share", { id, refs });
return resp.data;
} catch (e) {
return { status: false, message: (e as Error).message, data: "" };
}
}
export async function toggleConversation(
dispatch: AppDispatch,
id: number,

View File

@ -0,0 +1,47 @@
import axios from "axios";
import {Message} from "./types.ts";
export type SharingForm = {
status: boolean;
message: string;
data: string;
}
export type ViewData = {
name: string;
username: string;
time: string;
messages: Message[];
};
export type ViewForm = {
status: boolean;
message: string;
data: ViewData | null;
}
export async function shareConversation(
id: number, refs: number[] = [-1],
): Promise<SharingForm> {
try {
const resp = await axios.post("/conversation/share", { id, refs });
return resp.data;
} catch (e) {
return { status: false, message: (e as Error).message, data: "" };
}
}
export async function viewConversation(
hash: string,
): Promise<ViewForm> {
try {
const resp = await axios.get(`/conversation/view?hash=${hash}`);
return resp.data as ViewForm;
} catch (e) {
return {
status: false,
message: (e as Error).message,
data: null,
}
}
}

View File

@ -1,10 +1,10 @@
import { Conversation } from "./conversation.ts";
export type Message = {
role: string;
content: string;
keyword?: string;
quota?: number;
role: string;
};
export type Id = number;

View File

@ -183,6 +183,8 @@ const resources = {
failed: "Share failed",
copied: "Copied",
"copied-description": "Link has been copied to clipboard",
"not-found": "Conversation not found",
"not-found-description": "Conversation not found, please check if the link is correct or the conversation has been deleted",
}
},
},
@ -353,6 +355,8 @@ const resources = {
failed: "分享失败",
copied: "复制成功",
"copied-description": "链接已复制到剪贴板",
"not-found": "对话未找到",
"not-found-description": "对话未找到,请检查链接是否正确或对话是否已被删除",
}
},
},
@ -534,6 +538,8 @@ const resources = {
failed: "Поделиться не удалось",
copied: "Скопировано",
"copied-description": "Ссылка скопирована в буфер обмена",
"not-found": "Разговор не найден",
"not-found-description": "Разговор не найден, пожалуйста, проверьте, правильная ли ссылка или разговор был удален",
}
},
},

View File

@ -3,6 +3,7 @@ import Home from "./routes/Home.tsx";
import NotFound from "./routes/NotFound.tsx";
import Auth from "./routes/Auth.tsx";
import Generation from "./routes/Generation.tsx";
import Sharing from "./routes/Sharing.tsx";
const router = createBrowserRouter([
{
@ -22,6 +23,11 @@ const router = createBrowserRouter([
path: "/generate",
Component: Generation,
},
{
id: "share",
path: "/share/:hash",
Component: Sharing,
}
]);
export default router;

View File

@ -23,10 +23,11 @@ import type { RootState } from "../store";
import { selectAuthenticated, selectInit } from "../store/auth.ts";
import { login, supportModels } from "../conf.ts";
import {
deleteConversation, shareConversation,
deleteConversation,
toggleConversation,
updateConversationList,
} from "../conversation/history.ts";
import { shareConversation } from "../conversation/sharing.ts";
import React, { useEffect, useRef, useState } from "react";
import {
filterMessage,

108
app/src/routes/Sharing.tsx Normal file
View File

@ -0,0 +1,108 @@
import "../assets/sharing.less";
import {useParams} from "react-router-dom";
import {viewConversation, ViewData, ViewForm} from "../conversation/sharing.ts";
import {copyClipboard, saveAsFile, useEffectAsync} from "../utils.ts";
import {useState} from "react";
import {Copy, File, HelpCircle, Loader2, MessagesSquare} from "lucide-react";
import {useTranslation} from "react-i18next";
import MessageSegment from "../components/Message.tsx";
import {Button} from "../components/ui/button.tsx";
import router from "../router.ts";
import {useToast} from "../components/ui/use-toast.ts";
type SharingFormProps = {
refer?: string;
data: ViewData | null;
}
function SharingForm({ refer, data }: SharingFormProps) {
if (data === null) return null;
const { t } = useTranslation();
const date = new Date(data.time);
const time = `${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
const value = JSON.stringify(data, null, 2);
const { toast } = useToast();
return (
<div className={`sharing-container`}>
<div className={`header`}>
<div className={`user`}>
<img src={`https://api.deeptrain.net/avatar/${data.username}`} alt="" />
<span>{data.username}</span>
</div>
<div className={`name`}>{data.name}</div>
<div className={`time`}>{time}</div>
</div>
<div className={`body`}>
{
data.messages.map((message, i) => (
<MessageSegment message={message} key={i} />
))
}
</div>
<div className={`action`}>
<Button variant={`outline`} onClick={async () => {
await copyClipboard(value);
toast({
title: t('share.copied'),
});
}}>
<Copy className={`h-4 w-4 mr-2`} />
{t('message.copy')}
</Button>
<Button variant={`outline`} onClick={() => saveAsFile("conversation.json", value)}>
<File className={`h-4 w-4 mr-2`} />
{t('message.save')}
</Button>
<Button variant={`outline`} onClick={async () => {
refer && sessionStorage.setItem('refer', refer);
await router.navigate('/');
}}>
<MessagesSquare className={`h-4 w-4 mr-2`} />
{t('message.use')}
</Button>
</div>
</div>
)
}
function Sharing() {
const { t } = useTranslation();
const { hash } = useParams();
const [setup, setSetup] = useState(false);
const [data, setData] = useState<ViewForm | null>(null);
useEffectAsync(async () => {
if (!hash || setup) return;
setSetup(true);
const resp = await viewConversation(hash as string);
setData(resp);
if (!resp.status) console.debug(`[sharing] error: ${resp.message}`);
}, []);
return (
<div className={`sharing-page`}>
{
data === null ? (
<div className={`loading`}>
<Loader2 className={`loader w-12 h-12`} />
</div>
) : (
data.status ? (
<SharingForm refer={hash} data={data.data} />
) : (
<div className={`error-container`}>
<HelpCircle className={`w-12 h-12 mb-2.5`} />
<p className={`title`}>{t('share.not-found')}</p>
<p className={`description`}>{t('share.not-found-description')}</p>
</div>
)
)
}
</div>
)
}
export default Sharing;

View File

@ -1,6 +1,7 @@
package main
import (
"chat/adapter/chatgpt"
"chat/addition"
"chat/auth"
"chat/manager"
@ -21,6 +22,8 @@ func main() {
app := gin.Default()
middleware.RegisterMiddleware(app)
fmt.Println(chatgpt.FilterKeys("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|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|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|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|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|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|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|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|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|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|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"))
return
{
auth.Register(app)
manager.Register(app)