mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 13:00:14 +09:00
update sharing using feature
This commit is contained in:
parent
0f345fbfa4
commit
9eb021a52e
@ -115,6 +115,9 @@ func (c *ChatInstance) Test() bool {
|
||||
Message: []globals.Message{{Role: "user", Content: "hi"}},
|
||||
Token: 1,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprintf("%s: test failed (%s)", c.GetApiKey(), err.Error()))
|
||||
}
|
||||
|
||||
return err == nil && len(result) > 0
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ import { useState } from "react";
|
||||
type ConversationSegmentProps = {
|
||||
conversation: ConversationInstance;
|
||||
current: number;
|
||||
operate: (conversation: { target: ConversationInstance, type: string }) => void;
|
||||
operate: (conversation: {
|
||||
target: ConversationInstance;
|
||||
type: string;
|
||||
}) => void;
|
||||
};
|
||||
function ConversationSegment({
|
||||
conversation,
|
||||
@ -45,10 +48,13 @@ function ConversationSegment({
|
||||
<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) => {
|
||||
<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>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import axios from "axios";
|
||||
|
||||
export const version = "3.4.2";
|
||||
export const deploy: boolean = false;
|
||||
export const version = "3.4.3";
|
||||
export const deploy: boolean = true;
|
||||
export let rest_api: string = "http://localhost:8094";
|
||||
export let ws_api: string = "ws://localhost:8094";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
||||
import { Message } from "./types.ts";
|
||||
import { event } from "../events/sharing.ts";
|
||||
|
||||
type ConversationCallback = (idx: number, message: Message[]) => void;
|
||||
|
||||
@ -18,6 +19,15 @@ export class Conversation {
|
||||
this.id = id;
|
||||
this.end = true;
|
||||
this.connection = new Connection(this.id);
|
||||
|
||||
if (id === -1 && this.idx === -1) {
|
||||
event.bind(({ refer, data }) => {
|
||||
console.log(
|
||||
`[conversation] load from sharing event (ref: ${refer}, length: ${data.length})`,
|
||||
);
|
||||
this.load(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setId(id: number): void {
|
||||
|
@ -11,6 +11,7 @@ import { useShared } from "../utils.ts";
|
||||
import { ChatProps } from "./connection.ts";
|
||||
import { supportModelConvertor } from "../conf.ts";
|
||||
import { AppDispatch } from "../store";
|
||||
import { event } from "../events/sharing.ts";
|
||||
|
||||
export class Manager {
|
||||
conversations: Record<number, Conversation>;
|
||||
@ -21,6 +22,17 @@ export class Manager {
|
||||
this.conversations = {};
|
||||
this.conversations[-1] = this.createConversation(-1);
|
||||
this.current = -1;
|
||||
|
||||
event.addEventListener(async (data) => {
|
||||
console.debug(`[manager] accept sharing event (refer: ${data.refer})`);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (this.dispatch) {
|
||||
this.toggle(this.dispatch, -1);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
public setDispatch(dispatch: AppDispatch): void {
|
||||
|
@ -5,7 +5,7 @@ export type SharingForm = {
|
||||
status: boolean;
|
||||
message: string;
|
||||
data: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type ViewData = {
|
||||
name: string;
|
||||
@ -18,10 +18,11 @@ export type ViewForm = {
|
||||
status: boolean;
|
||||
message: string;
|
||||
data: ViewData | null;
|
||||
}
|
||||
};
|
||||
|
||||
export async function shareConversation(
|
||||
id: number, refs: number[] = [-1],
|
||||
id: number,
|
||||
refs: number[] = [-1],
|
||||
): Promise<SharingForm> {
|
||||
try {
|
||||
const resp = await axios.post("/conversation/share", { id, refs });
|
||||
@ -31,9 +32,7 @@ export async function shareConversation(
|
||||
}
|
||||
}
|
||||
|
||||
export async function viewConversation(
|
||||
hash: string,
|
||||
): Promise<ViewForm> {
|
||||
export async function viewConversation(hash: string): Promise<ViewForm> {
|
||||
try {
|
||||
const resp = await axios.get(`/conversation/view?hash=${hash}`);
|
||||
return resp.data as ViewForm;
|
||||
@ -42,6 +41,6 @@ export async function viewConversation(
|
||||
status: false,
|
||||
message: (e as Error).message,
|
||||
data: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
12
app/src/events/sharing.ts
Normal file
12
app/src/events/sharing.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { EventCommitter } from "./struct.ts";
|
||||
import { Message } from "../conversation/types.ts";
|
||||
|
||||
export type SharingEvent = {
|
||||
refer: string;
|
||||
data: Message[];
|
||||
};
|
||||
|
||||
export const event = new EventCommitter<SharingEvent>({
|
||||
name: "sharing",
|
||||
destroyedAfterTrigger: true,
|
||||
});
|
51
app/src/events/struct.ts
Normal file
51
app/src/events/struct.ts
Normal file
@ -0,0 +1,51 @@
|
||||
export type EventCommitterProps = {
|
||||
name: string;
|
||||
destroyedAfterTrigger?: boolean;
|
||||
};
|
||||
|
||||
export class EventCommitter<T> {
|
||||
name: string;
|
||||
trigger: ((data: T) => void) | undefined;
|
||||
listeners: ((data: T) => void)[] = [];
|
||||
destroyedAfterTrigger: boolean;
|
||||
|
||||
constructor({ name, destroyedAfterTrigger = false }: EventCommitterProps) {
|
||||
this.name = name;
|
||||
this.destroyedAfterTrigger = destroyedAfterTrigger;
|
||||
}
|
||||
|
||||
protected setTrigger(trigger: (data: T) => void) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
protected clearTrigger() {
|
||||
this.trigger = undefined;
|
||||
}
|
||||
|
||||
protected triggerEvent(data: T) {
|
||||
this.trigger && this.trigger(data);
|
||||
if (this.destroyedAfterTrigger) this.clearTrigger();
|
||||
|
||||
this.listeners.forEach((listener) => listener(data));
|
||||
}
|
||||
|
||||
public emit(data: T) {
|
||||
this.triggerEvent(data);
|
||||
}
|
||||
|
||||
public bind(trigger: (data: T) => void) {
|
||||
this.setTrigger(trigger);
|
||||
}
|
||||
|
||||
public addEventListener(listener: (data: T) => void) {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
public removeEventListener(listener: (data: T) => void) {
|
||||
this.listeners = this.listeners.filter((item) => item !== listener);
|
||||
}
|
||||
|
||||
public clearEventListener() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
@ -178,14 +178,15 @@ const resources = {
|
||||
"share-conversation": "Share Conversation",
|
||||
description: "Share this conversation with others: ",
|
||||
"copy-link": "Copy Link",
|
||||
"view": "View",
|
||||
view: "View",
|
||||
success: "Share success",
|
||||
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",
|
||||
}
|
||||
"not-found-description":
|
||||
"Conversation not found, please check if the link is correct or the conversation has been deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
cn: {
|
||||
@ -350,14 +351,15 @@ const resources = {
|
||||
"share-conversation": "分享对话",
|
||||
description: "将此对话与他人分享:",
|
||||
"copy-link": "复制链接",
|
||||
"view": "查看",
|
||||
view: "查看",
|
||||
success: "分享成功",
|
||||
failed: "分享失败",
|
||||
copied: "复制成功",
|
||||
"copied-description": "链接已复制到剪贴板",
|
||||
"not-found": "对话未找到",
|
||||
"not-found-description": "对话未找到,请检查链接是否正确或对话是否已被删除",
|
||||
}
|
||||
"not-found-description":
|
||||
"对话未找到,请检查链接是否正确或对话是否已被删除",
|
||||
},
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
@ -533,14 +535,15 @@ const resources = {
|
||||
"share-conversation": "Поделиться разговором",
|
||||
description: "Поделитесь этим разговором с другими: ",
|
||||
"copy-link": "Скопировать ссылку",
|
||||
"view": "Посмотреть",
|
||||
view: "Посмотреть",
|
||||
success: "Поделиться успешно",
|
||||
failed: "Поделиться не удалось",
|
||||
copied: "Скопировано",
|
||||
"copied-description": "Ссылка скопирована в буфер обмена",
|
||||
"not-found": "Разговор не найден",
|
||||
"not-found-description": "Разговор не найден, пожалуйста, проверьте, правильная ли ссылка или разговор был удален",
|
||||
}
|
||||
"not-found-description":
|
||||
"Разговор не найден, пожалуйста, проверьте, правильная ли ссылка или разговор был удален",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ const router = createBrowserRouter([
|
||||
id: "share",
|
||||
path: "/share/:hash",
|
||||
Component: Sharing,
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
export default router;
|
||||
|
@ -4,7 +4,8 @@ import { Input } from "../components/ui/input.tsx";
|
||||
import { Toggle } from "../components/ui/toggle.tsx";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight, Copy,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
FolderKanban,
|
||||
Globe,
|
||||
LogIn,
|
||||
@ -35,7 +36,8 @@ import {
|
||||
formatMessage,
|
||||
mobile,
|
||||
useAnimation,
|
||||
useEffectAsync, copyClipboard,
|
||||
useEffectAsync,
|
||||
copyClipboard,
|
||||
} from "../utils.ts";
|
||||
import { toast, useToast } from "../components/ui/use-toast.ts";
|
||||
import { ConversationInstance, Message } from "../conversation/types.ts";
|
||||
@ -74,8 +76,7 @@ function SideBar() {
|
||||
const open = useSelector((state: RootState) => state.menu.open);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
const current = useSelector(selectCurrent);
|
||||
const [operateConversation, setOperateConversation] =
|
||||
useState<{
|
||||
const [operateConversation, setOperateConversation] = useState<{
|
||||
target: ConversationInstance | null;
|
||||
type: string;
|
||||
}>({ target: null, type: "" });
|
||||
@ -140,7 +141,10 @@ function SideBar() {
|
||||
)}
|
||||
</div>
|
||||
<AlertDialog
|
||||
open={operateConversation.type === "delete" && !!operateConversation.target}
|
||||
open={
|
||||
operateConversation.type === "delete" &&
|
||||
!!operateConversation.target
|
||||
}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setOperateConversation({ target: null, type: "" });
|
||||
}}
|
||||
@ -194,16 +198,17 @@ function SideBar() {
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog
|
||||
open={operateConversation.type === "share" && !!operateConversation.target}
|
||||
open={
|
||||
operateConversation.type === "share" &&
|
||||
!!operateConversation.target
|
||||
}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setOperateConversation({ target: null, type: "" });
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("share.title")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("share.title")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("share.description")}
|
||||
<strong className={`conversation-name`}>
|
||||
@ -223,9 +228,13 @@ function SideBar() {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const resp = await shareConversation(operateConversation?.target?.id || -1);
|
||||
if (resp.status) setShared(`${location.origin}/share/${resp.data}`);
|
||||
else toast({
|
||||
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,
|
||||
});
|
||||
@ -250,19 +259,21 @@ function SideBar() {
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("share.success")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("share.success")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<div className={`share-wrapper mt-4 mb-2`}>
|
||||
<Input value={shared} />
|
||||
<Button variant={`default`} size={`icon`} onClick={async () => {
|
||||
<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>
|
||||
|
@ -1,6 +1,10 @@
|
||||
import "../assets/sharing.less";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {viewConversation, ViewData, ViewForm} from "../conversation/sharing.ts";
|
||||
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";
|
||||
@ -9,17 +13,21 @@ 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";
|
||||
import { event } from "../events/sharing.ts";
|
||||
import { Message } from "../conversation/types.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 time = `${
|
||||
date.getMonth() + 1
|
||||
}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
|
||||
const value = JSON.stringify(data, null, 2);
|
||||
const { toast } = useToast();
|
||||
|
||||
@ -27,43 +35,56 @@ function SharingForm({ refer, data }: SharingFormProps) {
|
||||
<div className={`sharing-container`}>
|
||||
<div className={`header`}>
|
||||
<div className={`user`}>
|
||||
<img src={`https://api.deeptrain.net/avatar/${data.username}`} alt="" />
|
||||
<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) => (
|
||||
{data.messages.map((message, i) => (
|
||||
<MessageSegment message={message} key={i} />
|
||||
))
|
||||
}
|
||||
))}
|
||||
</div>
|
||||
<div className={`action`}>
|
||||
<Button variant={`outline`} onClick={async () => {
|
||||
<Button
|
||||
variant={`outline`}
|
||||
onClick={async () => {
|
||||
await copyClipboard(value);
|
||||
toast({
|
||||
title: t('share.copied'),
|
||||
title: t("share.copied"),
|
||||
});
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Copy className={`h-4 w-4 mr-2`} />
|
||||
{t('message.copy')}
|
||||
{t("message.copy")}
|
||||
</Button>
|
||||
<Button variant={`outline`} onClick={() => saveAsFile("conversation.json", value)}>
|
||||
<Button
|
||||
variant={`outline`}
|
||||
onClick={() => saveAsFile("conversation.json", value)}
|
||||
>
|
||||
<File className={`h-4 w-4 mr-2`} />
|
||||
{t('message.save')}
|
||||
{t("message.save")}
|
||||
</Button>
|
||||
<Button variant={`outline`} onClick={async () => {
|
||||
refer && sessionStorage.setItem('refer', refer);
|
||||
await router.navigate('/');
|
||||
}}>
|
||||
<Button
|
||||
variant={`outline`}
|
||||
onClick={async () => {
|
||||
event.emit({
|
||||
refer: refer as string,
|
||||
data: data?.messages as Message[],
|
||||
});
|
||||
await router.navigate("/");
|
||||
}}
|
||||
>
|
||||
<MessagesSquare className={`h-4 w-4 mr-2`} />
|
||||
{t('message.use')}
|
||||
{t("message.use")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function Sharing() {
|
||||
@ -84,25 +105,21 @@ function Sharing() {
|
||||
|
||||
return (
|
||||
<div className={`sharing-page`}>
|
||||
{
|
||||
data === null ? (
|
||||
{data === null ? (
|
||||
<div className={`loading`}>
|
||||
<Loader2 className={`loader w-12 h-12`} />
|
||||
</div>
|
||||
) : (
|
||||
data.status ? (
|
||||
) : 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>
|
||||
<p className={`title`}>{t("share.not-found")}</p>
|
||||
<p className={`description`}>{t("share.not-found-description")}</p>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Sharing;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { getKey } from "../conversation/addition.ts";
|
||||
import { AppDispatch } from "./index.ts";
|
||||
import { AppDispatch, RootState } from "./index.ts";
|
||||
|
||||
export const apiSlice = createSlice({
|
||||
name: "api",
|
||||
@ -31,8 +31,8 @@ export const { toggleDialog, setDialog, openDialog, closeDialog, setKey } =
|
||||
apiSlice.actions;
|
||||
export default apiSlice.reducer;
|
||||
|
||||
export const dialogSelector = (state: any): boolean => state.api.dialog;
|
||||
export const keySelector = (state: any): string => state.api.key;
|
||||
export const dialogSelector = (state: RootState): boolean => state.api.dialog;
|
||||
export const keySelector = (state: RootState): string => state.api.key;
|
||||
|
||||
export const getApiKey = async (dispatch: AppDispatch) => {
|
||||
const response = await getKey();
|
||||
|
3
main.go
3
main.go
@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"chat/adapter/chatgpt"
|
||||
"chat/addition"
|
||||
"chat/auth"
|
||||
"chat/manager"
|
||||
@ -22,8 +21,6 @@ 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)
|
||||
|
Loading…
Reference in New Issue
Block a user