update sharing

This commit is contained in:
Zhang Minghan 2023-10-18 20:25:12 +08:00
parent fd8c2d6eb7
commit 8e02dae8d3
16 changed files with 366 additions and 123 deletions

View File

@ -92,10 +92,13 @@
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;
background: var(--conversation-card); background: var(--conversation-card);
.delete { .more {
color: hsl(var(--text-secondary)); color: hsl(var(--text-secondary));
display: none; display: none;
transition: 0.2s; transition: 0.2s;
opacity: 0;
border: 1px solid var(--border);
outline: 0;
&:hover { &:hover {
color: hsl(var(--text)); color: hsl(var(--text));
@ -109,8 +112,9 @@
display: none; display: none;
} }
.delete { .more {
display: block; display: block;
opacity: 1;
} }
} }
@ -332,3 +336,21 @@
} }
} }
} }
.share-wrapper {
display: flex;
flex-direction: row;
gap: 6px;
width: 100%;
input {
text-align: center;
font-size: 16px;
cursor: pointer;
flex-grow: 1;
}
button {
flex-shrink: 0;
}
}

View File

@ -17,12 +17,14 @@
} }
.select-group-item { .select-group-item {
padding: 0.35rem 0.5rem; padding: 0.35rem 0.8rem;
border: 1px solid hsl(var(--border));
border-radius: 4px; border-radius: 4px;
transition: .2s; transition: .2s;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
background: hsl(var(--accent-secondary)); background: hsl(var(--background));
color: hsl(var(--text));
&:hover { &:hover {
background: hsl(var(--accent)); background: hsl(var(--accent));
@ -30,6 +32,7 @@
&.active { &.active {
background: hsl(var(--text)); background: hsl(var(--text));
border-color: hsl(var(--border-hover));
color: hsl(var(--background)); color: hsl(var(--background));
} }
} }

View File

@ -13,7 +13,7 @@ import { Textarea } from "./ui/textarea.tsx";
import Markdown from "./Markdown.tsx"; import Markdown from "./Markdown.tsx";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Toggle } from "./ui/toggle.tsx"; import { Toggle } from "./ui/toggle.tsx";
import {mobile} from "../utils.ts"; import { mobile } from "../utils.ts";
type RichEditorProps = { type RichEditorProps = {
value: string; value: string;
@ -103,9 +103,9 @@ function RichEditor({
</div> </div>
<div className={`editor-wrapper`}> <div className={`editor-wrapper`}>
<div <div
className={`editor-object ${ className={`editor-object ${openInput ? "show-editor" : ""} ${
openInput ? "show-editor" : "" openPreview ? "show-preview" : ""
} ${openPreview ? "show-preview" : ""}`} }`}
> >
{openInput && ( {openInput && (
<Textarea <Textarea
@ -124,7 +124,7 @@ function RichEditor({
</div> </div>
</div> </div>
</div> </div>
) );
} }
function EditorProvider(props: RichEditorProps) { function EditorProvider(props: RichEditorProps) {
@ -134,9 +134,7 @@ function EditorProvider(props: RichEditorProps) {
<> <>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<div <div className={`editor-action active ${props.className}`}>
className={`editor-action active ${props.className}`}
>
<Edit className={`h-3.5 w-3.5`} /> <Edit className={`h-3.5 w-3.5`} />
</div> </div>
</DialogTrigger> </DialogTrigger>

View File

@ -5,7 +5,9 @@ function ProjectLink() {
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={() => window.open("https://github.com/Deeptrain-Community/chatnio")} onClick={() =>
window.open("https://github.com/Deeptrain-Community/chatnio")
}
> >
<svg <svg
viewBox="0 0 438.549 438.549" viewBox="0 0 438.549 438.549"

View File

@ -1,9 +1,9 @@
import { useRegisterSW } from 'virtual:pwa-register/react' import { useRegisterSW } from "virtual:pwa-register/react";
import {version} from "../conf.ts"; import { version } from "../conf.ts";
import {useTranslation} from "react-i18next"; import { useTranslation } from "react-i18next";
import {useToast} from "./ui/use-toast.ts"; import { useToast } from "./ui/use-toast.ts";
import {useEffect} from "react"; import { useEffect } from "react";
import {ToastAction} from "./ui/toast.tsx"; import { ToastAction } from "./ui/toast.tsx";
function ReloadPrompt() { function ReloadPrompt() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -18,36 +18,42 @@ function ReloadPrompt() {
console.debug(`[service] service worker registered (version ${version})`); console.debug(`[service] service worker registered (version ${version})`);
}, },
onRegisterError(error) { onRegisterError(error) {
console.log(`[service] service worker registration failed: ${error.message}`); console.log(
`[service] service worker registration failed: ${error.message}`,
);
}, },
}); });
const before = localStorage.getItem('version') || ''; const before = localStorage.getItem("version") || "";
if (before.length > 0 && before !== version) { if (before.length > 0 && before !== version) {
toast({ toast({
title: t('service.update-success'), title: t("service.update-success"),
description: t('service.update-success-prompt'), description: t("service.update-success-prompt"),
}) });
console.debug(`[service] service worker updated (from ${before} to ${version})`); console.debug(
`[service] service worker updated (from ${before} to ${version})`,
);
} }
localStorage.setItem('version', version); localStorage.setItem("version", version);
useEffect(() => { useEffect(() => {
if (offlineReady) { if (offlineReady) {
toast({ toast({
title: t('service.offline-title'), title: t("service.offline-title"),
description: t('service.offline'), description: t("service.offline"),
}) });
} }
if (needRefresh) { if (needRefresh) {
toast({ toast({
title: t('service.title'), title: t("service.title"),
description: t('service.description'), description: t("service.description"),
action: ( action: (
<ToastAction altText={t('service.update')} onClick={() => updateServiceWorker(true)}> <ToastAction
{t('service.update')} altText={t("service.update")}
onClick={() => updateServiceWorker(true)}
>
{t("service.update")}
</ToastAction> </ToastAction>
), ),
}); });
@ -61,4 +67,3 @@ function ReloadPrompt() {
} }
export default ReloadPrompt; export default ReloadPrompt;

View File

@ -0,0 +1,89 @@
import { toggleConversation } from "../../conversation/history.ts";
import { filterMessage, mobile } from "../../utils.ts";
import { setMenu } from "../../store/menu.ts";
import {MessageSquare, MoreHorizontal, Share2, Trash2} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu.tsx";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import { ConversationInstance } from "../../conversation/types.ts";
import { useState } from "react";
type ConversationSegmentProps = {
conversation: ConversationInstance;
current: number;
operate: (conversation: { target: ConversationInstance, type: string }) => void;
};
function ConversationSegment({
conversation,
current,
operate,
}: ConversationSegmentProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [offset, setOffset] = useState(0);
return (
<div
className={`conversation ${current === conversation.id ? "active" : ""}`}
onClick={async (e) => {
const target = e.target as HTMLElement;
if (
target.classList.contains("delete") ||
target.parentElement?.classList.contains("delete")
)
return;
await toggleConversation(dispatch, conversation.id);
if (mobile) dispatch(setMenu(false));
}}
>
<MessageSquare className={`h-4 w-4 mr-1`} />
<div className={`title`}>{filterMessage(conversation.name)}</div>
<div className={`id`}>{conversation.id}</div>
<DropdownMenu open={open} onOpenChange={(state: boolean) => {
setOpen(state);
if (state) setOffset(new Date().getTime());
}}>
<DropdownMenuTrigger>
<MoreHorizontal className={`more h-5 w-5 p-0.5`} />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={(e) => {
// prevent click event from opening the dropdown menu
if (offset + 500 > new Date().getTime()) return;
e.preventDefault();
e.stopPropagation();
operate({ target: conversation, type: "delete" });
setOpen(false);
}}
>
<Trash2 className={`more h-4 w-4 mx-1`} />
{t("conversation.delete-conversation")}
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
operate({ target: conversation, type: "share" });
setOpen(false);
}}
>
<Share2 className={`more h-4 w-4 mx-1`} />
{t("share.share-conversation")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
export default ConversationSegment;

View File

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

View File

@ -4,6 +4,12 @@ import { setHistory } from "../store/chat.ts";
import { manager } from "./manager.ts"; import { manager } from "./manager.ts";
import { AppDispatch } from "../store"; import { AppDispatch } from "../store";
type SharingForm = {
status: boolean;
message: string;
data: string;
}
export async function updateConversationList( export async function updateConversationList(
dispatch: AppDispatch, dispatch: AppDispatch,
): Promise<void> { ): Promise<void> {
@ -36,6 +42,17 @@ export async function deleteConversation(
return true; 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( export async function toggleConversation(
dispatch: AppDispatch, dispatch: AppDispatch,
id: number, id: number,

View File

@ -40,6 +40,7 @@ const resources = {
"This action cannot be undone. This will permanently delete the conversation ", "This action cannot be undone. This will permanently delete the conversation ",
cancel: "Cancel", cancel: "Cancel",
delete: "Delete", delete: "Delete",
"delete-conversation": "Delete Conversation",
"delete-success": "Conversation deleted", "delete-success": "Conversation deleted",
"delete-success-prompt": "Conversation has been deleted.", "delete-success-prompt": "Conversation has been deleted.",
"delete-failed": "Delete failed", "delete-failed": "Delete failed",
@ -164,13 +165,24 @@ const resources = {
"learn-more": "Learn more", "learn-more": "Learn more",
}, },
service: { service: {
"title": "New Version Available", title: "New Version Available",
"description": "A new version is available. Do you want to update now?", description: "A new version is available. Do you want to update now?",
"update": "Update", update: "Update",
"offline-title": "Offline Mode", "offline-title": "Offline Mode",
"offline": "App is currently offline.", offline: "App is currently offline.",
"update-success": "Update Success", "update-success": "Update Success",
"update-success-prompt": "You have been updated to the latest version.", "update-success-prompt": "You have been updated to the latest version.",
},
share: {
title: "Share",
"share-conversation": "Share Conversation",
description: "Share this conversation with others: ",
"copy-link": "Copy Link",
"view": "View",
success: "Share success",
failed: "Share failed",
copied: "Copied",
"copied-description": "Link has been copied to clipboard",
} }
}, },
}, },
@ -196,18 +208,19 @@ const resources = {
close: "关闭", close: "关闭",
edit: "编辑", edit: "编辑",
conversation: { conversation: {
title: "话", title: "话",
empty: "空空如也", empty: "空空如也",
"refresh-failed": "刷新失败", "refresh-failed": "刷新失败",
"refresh-failed-prompt": "请求出错,请重试。", "refresh-failed-prompt": "请求出错,请重试。",
"remove-title": "是否确定?", "remove-title": "是否确定?",
"remove-description": "此操作无法撤消。这将永久删除话 ", "remove-description": "此操作无法撤消。这将永久删除话 ",
cancel: "取消", cancel: "取消",
delete: "删除", delete: "删除",
"delete-success": "会话已删除", "delete-conversation": "删除对话",
"delete-success-prompt": "会话已删除。", "delete-success": "对话已删除",
"delete-success-prompt": "对话已删除。",
"delete-failed": "删除失败", "delete-failed": "删除失败",
"delete-failed-prompt": "删除话失败,请检查您的网络并重试。", "delete-failed-prompt": "删除话失败,请检查您的网络并重试。",
}, },
chat: { chat: {
web: "联网搜索功能", web: "联网搜索功能",
@ -322,13 +335,24 @@ const resources = {
"learn-more": "了解更多", "learn-more": "了解更多",
}, },
service: { service: {
"title": "发现新版本", title: "发现新版本",
"description": "发现新版本,是否立即更新?", description: "发现新版本,是否立即更新?",
"update": "更新", update: "更新",
"offline-title": "离线模式", "offline-title": "离线模式",
"offline": "应用当前处于离线状态。", offline: "应用当前处于离线状态。",
"update-success": "更新成功", "update-success": "更新成功",
"update-success-prompt": "您已更新至最新版本。", "update-success-prompt": "您已更新至最新版本。",
},
share: {
title: "分享",
"share-conversation": "分享对话",
description: "将此对话与他人分享:",
"copy-link": "复制链接",
"view": "查看",
success: "分享成功",
failed: "分享失败",
copied: "复制成功",
"copied-description": "链接已复制到剪贴板",
} }
}, },
}, },
@ -367,6 +391,7 @@ const resources = {
"Это действие нельзя отменить. Это навсегда удалит разговор ", "Это действие нельзя отменить. Это навсегда удалит разговор ",
cancel: "Отмена", cancel: "Отмена",
delete: "Удалить", delete: "Удалить",
"delete-conversation": "Удалить разговор",
"delete-success": "Разговор удален", "delete-success": "Разговор удален",
"delete-success-prompt": "Разговор был удален.", "delete-success-prompt": "Разговор был удален.",
"delete-failed": "Ошибка удаления", "delete-failed": "Ошибка удаления",
@ -491,13 +516,24 @@ const resources = {
"learn-more": "Узнать больше", "learn-more": "Узнать больше",
}, },
service: { service: {
"title": "Доступна новая версия", title: "Доступна новая версия",
"description": "Доступна новая версия. Хотите обновить сейчас?", description: "Доступна новая версия. Хотите обновить сейчас?",
"update": "Обновить", update: "Обновить",
"offline-title": "Режим оффлайн", "offline-title": "Режим оффлайн",
"offline": "Приложение в настоящее время находится в автономном режиме.", offline: "Приложение в настоящее время находится в автономном режиме.",
"update-success": "Обновление успешно", "update-success": "Обновление успешно",
"update-success-prompt": "Вы обновились до последней версии.", "update-success-prompt": "Вы обновились до последней версии.",
},
share: {
title: "Поделиться",
"share-conversation": "Поделиться разговором",
description: "Поделитесь этим разговором с другими: ",
"copy-link": "Скопировать ссылку",
"view": "Посмотреть",
success: "Поделиться успешно",
failed: "Поделиться не удалось",
copied: "Скопировано",
"copied-description": "Ссылка скопирована в буфер обмена",
} }
}, },
}, },

View File

@ -4,14 +4,12 @@ import { Input } from "../components/ui/input.tsx";
import { Toggle } from "../components/ui/toggle.tsx"; import { Toggle } from "../components/ui/toggle.tsx";
import { import {
ChevronDown, ChevronDown,
ChevronRight, ChevronRight, Copy,
FolderKanban, FolderKanban,
Globe, Globe,
LogIn, LogIn,
MessageSquare,
Plus, Plus,
RotateCw, RotateCw,
Trash2,
} from "lucide-react"; } from "lucide-react";
import { Button } from "../components/ui/button.tsx"; import { Button } from "../components/ui/button.tsx";
import { import {
@ -25,7 +23,7 @@ import type { RootState } from "../store";
import { selectAuthenticated, selectInit } from "../store/auth.ts"; import { selectAuthenticated, selectInit } from "../store/auth.ts";
import { login, supportModels } from "../conf.ts"; import { login, supportModels } from "../conf.ts";
import { import {
deleteConversation, deleteConversation, shareConversation,
toggleConversation, toggleConversation,
updateConversationList, updateConversationList,
} from "../conversation/history.ts"; } from "../conversation/history.ts";
@ -36,7 +34,7 @@ import {
formatMessage, formatMessage,
mobile, mobile,
useAnimation, useAnimation,
useEffectAsync, useEffectAsync, copyClipboard,
} from "../utils.ts"; } from "../utils.ts";
import { toast, useToast } from "../components/ui/use-toast.ts"; import { toast, useToast } from "../components/ui/use-toast.ts";
import { ConversationInstance, Message } from "../conversation/types.ts"; import { ConversationInstance, Message } from "../conversation/types.ts";
@ -67,6 +65,7 @@ import FileProvider, { FileObject } from "../components/FileProvider.tsx";
import router from "../router.ts"; import router from "../router.ts";
import SelectGroup from "../components/SelectGroup.tsx"; import SelectGroup from "../components/SelectGroup.tsx";
import EditorProvider from "../components/EditorProvider.tsx"; import EditorProvider from "../components/EditorProvider.tsx";
import ConversationSegment from "../components/home/ConversationSegment.tsx";
function SideBar() { function SideBar() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -74,15 +73,20 @@ function SideBar() {
const open = useSelector((state: RootState) => state.menu.open); const open = useSelector((state: RootState) => state.menu.open);
const auth = useSelector(selectAuthenticated); const auth = useSelector(selectAuthenticated);
const current = useSelector(selectCurrent); const current = useSelector(selectCurrent);
const [removeConversation, setRemoveConversation] = const [operateConversation, setOperateConversation] =
useState<ConversationInstance | null>(null); useState<{
target: ConversationInstance | null;
type: string;
}>({ target: null, type: "" });
const { toast } = useToast(); const { toast } = useToast();
const history: ConversationInstance[] = useSelector(selectHistory); const history: ConversationInstance[] = useSelector(selectHistory);
const refresh = useRef(null); const refresh = useRef(null);
const [shared, setShared] = useState<string>("");
useEffectAsync(async () => { useEffectAsync(async () => {
await updateConversationList(dispatch); await updateConversationList(dispatch);
}, []); }, []);
// @ts-ignore
return ( return (
<div className={`sidebar ${open ? "open" : ""}`}> <div className={`sidebar ${open ? "open" : ""}`}>
{auth ? ( {auth ? (
@ -123,45 +127,21 @@ function SideBar() {
<div className={`conversation-list`}> <div className={`conversation-list`}>
{history.length ? ( {history.length ? (
history.map((conversation, i) => ( history.map((conversation, i) => (
<div <ConversationSegment
className={`conversation ${ operate={setOperateConversation}
current === conversation.id ? "active" : "" conversation={conversation}
}`} current={current}
key={i} key={i}
onClick={async (e) => { />
const target = e.target as HTMLElement;
if (
target.classList.contains("delete") ||
target.parentElement?.classList.contains("delete")
)
return;
await toggleConversation(dispatch, conversation.id);
if (mobile) dispatch(setMenu(false));
}}
>
<MessageSquare className={`h-4 w-4 mr-1`} />
<div className={`title`}>
{filterMessage(conversation.name)}
</div>
<div className={`id`}>{conversation.id}</div>
<Trash2
className={`delete h-4 w-4`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setRemoveConversation(conversation);
}}
/>
</div>
)) ))
) : ( ) : (
<div className={`empty`}>{t("conversation.empty")}</div> <div className={`empty`}>{t("conversation.empty")}</div>
)} )}
</div> </div>
<AlertDialog <AlertDialog
open={removeConversation !== null} open={operateConversation.type === "delete" && !!operateConversation.target}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) setRemoveConversation(null); if (!open) setOperateConversation({ target: null, type: "" });
}} }}
> >
<AlertDialogContent> <AlertDialogContent>
@ -173,7 +153,7 @@ function SideBar() {
{t("conversation.remove-description")} {t("conversation.remove-description")}
<strong className={`conversation-name`}> <strong className={`conversation-name`}>
{extractMessage( {extractMessage(
filterMessage(removeConversation?.name || ""), filterMessage(operateConversation?.target?.name || ""),
)} )}
</strong> </strong>
{t("end")} {t("end")}
@ -191,7 +171,7 @@ function SideBar() {
if ( if (
await deleteConversation( await deleteConversation(
dispatch, dispatch,
removeConversation?.id || -1, operateConversation?.target?.id || -1,
) )
) )
toast({ toast({
@ -203,7 +183,7 @@ function SideBar() {
title: t("conversation.delete-failed"), title: t("conversation.delete-failed"),
description: t("conversation.delete-failed-prompt"), description: t("conversation.delete-failed-prompt"),
}); });
setRemoveConversation(null); setOperateConversation({ target: null, type: "" });
}} }}
> >
{t("conversation.delete")} {t("conversation.delete")}
@ -211,6 +191,96 @@ function SideBar() {
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
<AlertDialog
open={operateConversation.type === "share" && !!operateConversation.target}
onOpenChange={(open) => {
if (!open) setOperateConversation({ target: null, type: "" });
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("share.title")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("share.description")}
<strong className={`conversation-name`}>
{extractMessage(
filterMessage(operateConversation?.target?.name || ""),
)}
</strong>
{t("end")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{t("conversation.cancel")}
</AlertDialogCancel>
<AlertDialogAction
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
const resp = await shareConversation(operateConversation?.target?.id || -1);
if (resp.status) setShared(`${location.origin}/share/${resp.data}`);
else toast({
title: t("share.failed"),
description: resp.message,
});
setOperateConversation({ target: null, type: "" });
}}
>
{t("share.title")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog
open={shared.length > 0}
onOpenChange={(open) => {
if (!open) {
setShared("");
setOperateConversation({ target: null, type: "" });
}
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("share.success")}
</AlertDialogTitle>
<AlertDialogDescription>
<div className={`share-wrapper mt-4 mb-2`}>
<Input value={shared} />
<Button variant={`default`} size={`icon`} onClick={async () => {
await copyClipboard(shared);
toast({
title: t("share.copied"),
description: t("share.copied-description"),
});
}}>
<Copy className={`h-4 w-4`} />
</Button>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("close")}</AlertDialogCancel>
<AlertDialogAction
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
window.open(shared, "_blank");
}}
>
{t("share.view")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div> </div>
) : ( ) : (
<Button className={`login-action`} variant={`default`} onClick={login}> <Button className={`login-action`} variant={`default`} onClick={login}>

View File

@ -71,9 +71,7 @@ function Package() {
</Button> </Button>
<Button <Button
variant={`default`} variant={`default`}
onClick={() => onClick={() => window.open("https://deeptrain.net/home/package")}
window.open("https://deeptrain.net/home/package")
}
> >
{t("pkg.go")} {t("pkg.go")}
</Button> </Button>

View File

@ -1,15 +1,15 @@
declare module 'virtual:pwa-register/react' { declare module "virtual:pwa-register/react" {
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-expect-error ignore when React is not installed // @ts-expect-error ignore when React is not installed
import type { Dispatch, SetStateAction } from 'react' import type { Dispatch, SetStateAction } from "react";
import type { RegisterSWOptions } from 'vite-plugin-pwa/types' import type { RegisterSWOptions } from "vite-plugin-pwa/types";
export type { RegisterSWOptions } export type { RegisterSWOptions };
export function useRegisterSW(options?: RegisterSWOptions): { export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: [boolean, Dispatch<SetStateAction<boolean>>] needRefresh: [boolean, Dispatch<SetStateAction<boolean>>];
offlineReady: [boolean, Dispatch<SetStateAction<boolean>>] offlineReady: [boolean, Dispatch<SetStateAction<boolean>>];
updateServiceWorker: (reloadPage?: boolean) => Promise<void> updateServiceWorker: (reloadPage?: boolean) => Promise<void>;
onRegistered: (registration: ServiceWorkerRegistration) => void onRegistered: (registration: ServiceWorkerRegistration) => void;
} };
} }

View File

@ -111,8 +111,7 @@ func CreateSharingTable(db *sql.DB) {
conversation_id INT, conversation_id INT,
refs TEXT, refs TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES auth(id), FOREIGN KEY (user_id) REFERENCES auth(id)
FOREIGN KEY (conversation_id) REFERENCES conversation(id)
); );
`) `)
if err != nil { if err != nil {

View File

@ -10,8 +10,8 @@ import (
) )
type ShareForm struct { type ShareForm struct {
ConversationId int64 `json:"conversation_id"` Id int64 `json:"id"`
Refs []int `json:"refs"` Refs []int `json:"refs"`
} }
func ListAPI(c *gin.Context) { func ListAPI(c *gin.Context) {
@ -121,18 +121,19 @@ func ShareAPI(c *gin.Context) {
return return
} }
if err := ShareConversation(db, user, form.ConversationId, form.Refs); err != nil { if hash, err := ShareConversation(db, user, form.Id, form.Refs); err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": false, "status": false,
"message": err.Error(), "message": err.Error(),
}) })
return return
} else {
c.JSON(http.StatusOK, gin.H{
"status": true,
"message": "",
"data": hash,
})
} }
c.JSON(http.StatusOK, gin.H{
"status": true,
"message": "",
})
} }
func ViewAPI(c *gin.Context) { func ViewAPI(c *gin.Context) {

View File

@ -9,5 +9,6 @@ func Register(app *gin.Engine) {
router.GET("/load", LoadAPI) router.GET("/load", LoadAPI)
router.GET("/delete", DeleteAPI) router.GET("/delete", DeleteAPI)
router.POST("/share", ShareAPI) router.POST("/share", ShareAPI)
router.GET("/view", ViewAPI)
} }
} }

View File

@ -30,9 +30,9 @@ func GetRef(refs []int) (result string) {
return strings.TrimSuffix(result, ",") return strings.TrimSuffix(result, ",")
} }
func ShareConversation(db *sql.DB, user *auth.User, id int64, refs []int) error { func ShareConversation(db *sql.DB, user *auth.User, id int64, refs []int) (string, error) {
if id < 0 || user == nil { if id < 0 || user == nil {
return nil return "", nil
} }
ref := GetRef(refs) ref := GetRef(refs)
@ -42,12 +42,14 @@ func ShareConversation(db *sql.DB, user *auth.User, id int64, refs []int) error
Refs: refs, Refs: refs,
}) })
_, err := db.Exec(` if _, err := db.Exec(`
INSERT INTO sharing (hash, user_id, conversation_id, refs) VALUES (?, ?, ?, ?) INSERT INTO sharing (hash, user_id, conversation_id, refs) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE refs = ? ON DUPLICATE KEY UPDATE refs = ?
`, hash, user.GetID(db), id, ref, ref) `, hash, user.GetID(db), id, ref, ref); err != nil {
return "", err
}
return err return hash, nil
} }
func GetSharedMessages(db *sql.DB, userId int64, conversationId int64, refs []string) []globals.Message { func GetSharedMessages(db *sql.DB, userId int64, conversationId int64, refs []string) []globals.Message {
@ -84,7 +86,7 @@ func GetSharedConversation(db *sql.DB, hash string) (*SharedForm, error) {
sharing.user_id, sharing.conversation_id sharing.user_id, sharing.conversation_id
FROM sharing FROM sharing
INNER JOIN auth ON auth.id = sharing.user_id INNER JOIN auth ON auth.id = sharing.user_id
INNER JOIN conversation ON conversation.id = sharing.conversation_id INNER JOIN conversation ON conversation.conversation_id = sharing.conversation_id AND conversation.user_id = sharing.user_id
WHERE sharing.hash = ? WHERE sharing.hash = ?
`, hash).Scan(&shared.Username, &ref, &updated, &shared.Name, &uid, &cid); err != nil { `, hash).Scan(&shared.Username, &ref, &updated, &shared.Name, &uid, &cid); err != nil {
return nil, err return nil, err