diff --git a/app/src/components/Message.tsx b/app/src/components/Message.tsx index 2a11976..d052504 100644 --- a/app/src/components/Message.tsx +++ b/app/src/components/Message.tsx @@ -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; }; function MessageSegment(props: MessageProps) { + const [ copied, setCopied ] = useState(""); const { t } = useTranslation(); + const ref = useRef(null); const { message } = props; + function updateSelection(): void { + if (ref.current) setCopied(getSelectionTextInArea(ref.current)); + } + return ( - + -
+
{message.quota && message.quota !== 0 ? ( @@ -66,6 +74,15 @@ function MessageSegment(props: MessageProps) {
+ { + copied.length > 0 && ( + copyClipboard(copied)} + > + {t("message.copy-area")} + + ) + } copyClipboard(filterMessage(message.content))} > diff --git a/app/src/conf.ts b/app/src/conf.ts index 3bdfb2a..f686cd1 100644 --- a/app/src/conf.ts +++ b/app/src/conf.ts @@ -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"; diff --git a/app/src/i18n.ts b/app/src/i18n.ts index ef96b3f..36e12ba 100644 --- a/app/src/i18n.ts +++ b/app/src/i18n.ts @@ -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: { diff --git a/app/src/utils.ts b/app/src/utils.ts index d753e8e..653e6d4 100644 --- a/app/src/utils.ts +++ b/app/src/utils.ts @@ -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); +} diff --git a/manager/chat.go b/manager/chat.go index 76f4f47..f5b9b79 100644 --- a/manager/chat.go +++ b/manager/chat.go @@ -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{ diff --git a/manager/completions.go b/manager/completions.go index e5f10dd..85e6e94 100644 --- a/manager/completions.go +++ b/manager/completions.go @@ -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, diff --git a/manager/transhipment.go b/manager/transhipment.go index 26cba57..098fa48 100644 --- a/manager/transhipment.go +++ b/manager/transhipment.go @@ -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 }()