add copy selection text feature

This commit is contained in:
Zhang Minghan 2023-10-28 16:56:35 +08:00
parent 67b574033b
commit 0014ce0818
7 changed files with 50 additions and 14 deletions

View File

@ -8,7 +8,7 @@ import {
Loader2,
MousePointerSquare,
Power,
RotateCcw,
RotateCcw, ScanText,
} from "lucide-react";
import {
ContextMenu,
@ -18,7 +18,7 @@ import {
} from "./ui/context-menu.tsx";
import {
copyClipboard,
filterMessage,
filterMessage, getSelectionTextInArea,
saveAsFile,
useInputValue,
} from "../utils.ts";
@ -29,21 +29,29 @@ import {
TooltipProvider,
TooltipTrigger,
} from "./ui/tooltip.tsx";
import {Ref, useRef, useState} from "react";
type MessageProps = {
message: Message;
end?: boolean;
onEvent?: (event: string) => void;
ref?: Ref<HTMLElement>;
};
function MessageSegment(props: MessageProps) {
const [ copied, setCopied ] = useState<string>("");
const { t } = useTranslation();
const ref = useRef(null);
const { message } = props;
function updateSelection(): void {
if (ref.current) setCopied(getSelectionTextInArea(ref.current));
}
return (
<ContextMenu>
<ContextMenu onOpenChange={updateSelection}>
<ContextMenuTrigger asChild>
<div className={`message ${message.role}`}>
<div className={`message ${message.role}`} ref={ref}>
<MessageContent {...props} />
{message.quota && message.quota !== 0 ? (
<TooltipProvider>
@ -66,6 +74,15 @@ function MessageSegment(props: MessageProps) {
</div>
</ContextMenuTrigger>
<ContextMenuContent>
{
copied.length > 0 && (
<ContextMenuItem
onClick={() => copyClipboard(copied)}
>
<ScanText className={`h-4 w-4 mr-2`} /> {t("message.copy-area")}
</ContextMenuItem>
)
}
<ContextMenuItem
onClick={() => copyClipboard(filterMessage(message.content))}
>

View File

@ -1,7 +1,7 @@
import axios from "axios";
import { Model } from "./conversation/types.ts";
export const version = "3.5.18";
export const version = "3.5.19";
export const dev: boolean = window.location.hostname === "localhost";
export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094";

View File

@ -59,6 +59,7 @@ const resources = {
copy: "Copy",
save: "Save as File",
use: "Use Message",
"copy-area": "Copy Selected Area",
},
"quota-description": "spending quota for the message",
buy: {
@ -261,6 +262,7 @@ const resources = {
copy: "复制",
save: "保存为文件",
use: "使用消息",
"copy-area": "复制选中区域",
},
"quota-description": "消息的配额支出",
buy: {
@ -464,6 +466,7 @@ const resources = {
copy: "Копировать",
save: "Сохранить как файл",
use: "Использовать сообщение",
"copy-area": "Копировать выбранную область",
},
"quota-description": "квота расходов на сообщение",
buy: {

View File

@ -270,3 +270,15 @@ export function getSelectionText(): string {
}
return "";
}
// browser compatibility issue
export function getSelectionTextInArea(el: HTMLElement): string {
const selection = window.getSelection();
if (!selection) return "";
const range = selection.getRangeAt(0);
const preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(el);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
const start = preSelectionRange.toString().length;
return el.innerText.slice(start, start + range.toString().length);
}

View File

@ -20,9 +20,13 @@ func GetErrorQuota(model string) float32 {
return utils.Multi[float32](globals.IsGPT4Model(model), -0xe, 0) // special value for error
}
func CollectQuota(c *gin.Context, user *auth.User, quota float32, reversible bool) {
func CollectQuota(c *gin.Context, user *auth.User, buffer *utils.Buffer, uncountable bool) {
db := utils.GetDBFromContext(c)
if !reversible && quota > 0 && user != nil {
quota := buffer.GetQuota()
if buffer.IsEmpty() {
return
}
if !uncountable && quota > 0 && user != nil {
user.UseQuota(db, quota)
}
}
@ -130,7 +134,7 @@ func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conve
if err != nil && err.Error() != "signal" {
globals.Warn(fmt.Sprintf("caught error from chat handler: %s (instance: %s, client: %s)", err, model, conn.GetCtx().ClientIP()))
CollectQuota(conn.GetCtx(), user, buffer.GetQuota(), plan)
CollectQuota(conn.GetCtx(), user, buffer, plan)
conn.Send(globals.ChatSegmentResponse{
Message: err.Error(),
Quota: GetErrorQuota(model),
@ -139,7 +143,7 @@ func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conve
return err.Error()
}
CollectQuota(conn.GetCtx(), user, buffer.GetQuota(), plan)
CollectQuota(conn.GetCtx(), user, buffer, plan)
if buffer.IsEmpty() {
conn.Send(globals.ChatSegmentResponse{

View File

@ -46,11 +46,11 @@ func NativeChatHandler(c *gin.Context, user *auth.User, model string, message []
buffer.Write(resp)
return nil
}); err != nil {
CollectQuota(c, user, buffer.GetQuota(), plan)
CollectQuota(c, user, buffer, plan)
return keyword, err.Error(), GetErrorQuota(model)
}
CollectQuota(c, user, buffer.GetQuota(), plan)
CollectQuota(c, user, buffer, plan)
SaveCacheData(c, &CacheProps{
Message: segment,

View File

@ -125,7 +125,7 @@ func sendTranshipmentResponse(c *gin.Context, form TranshipmentForm, id string,
globals.Warn(fmt.Sprintf("error from chat request api: %s", err.Error()))
}
CollectQuota(c, user, buffer.GetQuota(), plan)
CollectQuota(c, user, buffer, plan)
c.JSON(http.StatusOK, TranshipmentResponse{
Id: id,
Object: "chat.completion",
@ -186,13 +186,13 @@ func sendStreamTranshipmentResponse(c *gin.Context, form TranshipmentForm, id st
return nil
}); err != nil {
channel <- getStreamTranshipmentForm(id, created, form, fmt.Sprintf("Error: %s", err.Error()), buffer, true)
CollectQuota(c, user, buffer.GetQuota(), plan)
CollectQuota(c, user, buffer, plan)
close(channel)
return
}
channel <- getStreamTranshipmentForm(id, created, form, "", buffer, true)
CollectQuota(c, user, buffer.GetQuota(), plan)
CollectQuota(c, user, buffer, plan)
close(channel)
return
}()