mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 13:30:13 +09:00
update utils
This commit is contained in:
parent
83213a60fb
commit
a4a2398045
@ -54,8 +54,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: -30px;
|
top: -34px;
|
||||||
right: 2px;
|
right: 0px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -63,7 +63,6 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin: 0 0 0 6px;
|
margin: 0 0 0 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
@ -6,15 +6,15 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./ui/dialog.tsx";
|
} from "./ui/dialog.tsx";
|
||||||
import {Maximize, Image, MenuSquare, PanelRight, XSquare} from "lucide-react";
|
import { Maximize, Image, MenuSquare, PanelRight, XSquare } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import "../assets/editor.less";
|
import "@/assets/editor.less";
|
||||||
import { Textarea } from "./ui/textarea.tsx";
|
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/device.ts";
|
||||||
import {Button} from "./ui/button.tsx";
|
import { Button } from "./ui/button.tsx";
|
||||||
|
|
||||||
type RichEditorProps = {
|
type RichEditorProps = {
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { AlertCircle, File, FileCheck, Plus, X } from "lucide-react";
|
import { AlertCircle, File, FileCheck, Plus, X } from "lucide-react";
|
||||||
import "../assets/file.less";
|
import "@/assets/file.less";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, AlertTitle } from "./ui/alert.tsx";
|
import { Alert, AlertTitle } from "./ui/alert.tsx";
|
||||||
import { useToast } from "./ui/use-toast.ts";
|
import { useToast } from "./ui/use-toast.ts";
|
||||||
import { useDraggableInput } from "../utils.ts";
|
import { useDraggableInput } from "@/utils/dom.ts";
|
||||||
|
|
||||||
export type FileObject = {
|
export type FileObject = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "./ui/dropdown-menu.tsx";
|
} from "./ui/dropdown-menu.tsx";
|
||||||
import { setLanguage } from "../i18n.ts";
|
import { setLanguage } from "@/i18n.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function I18nProvider() {
|
function I18nProvider() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import "../assets/loader.less";
|
import "@/assets/loader.less";
|
||||||
|
|
||||||
type LoaderProps = {
|
type LoaderProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -5,16 +5,16 @@ import remarkGfm from "remark-gfm";
|
|||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import rehypeKatex from "rehype-katex";
|
import rehypeKatex from "rehype-katex";
|
||||||
import { parseFile } from "./plugins/file.tsx";
|
import { parseFile } from "./plugins/file.tsx";
|
||||||
import "../assets/markdown/all.less";
|
import "@/assets/markdown/all.less";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { openDialog as openQuotaDialog } from "../store/quota.ts";
|
import { openDialog as openQuotaDialog } from "@/store/quota.ts";
|
||||||
import { openDialog as openSubscriptionDialog } from "../store/subscription.ts";
|
import { openDialog as openSubscriptionDialog } from "@/store/subscription.ts";
|
||||||
import { AppDispatch } from "../store";
|
import { AppDispatch } from "@/store";
|
||||||
import {Copy} from "lucide-react";
|
import { Copy } from "lucide-react";
|
||||||
import {copyClipboard} from "../utils.ts";
|
import { copyClipboard } from "@/utils/dom.ts";
|
||||||
import {useToast} from "./ui/use-toast.ts";
|
import { useToast } from "./ui/use-toast.ts";
|
||||||
import {useTranslation} from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type MarkdownProps = {
|
type MarkdownProps = {
|
||||||
children: string;
|
children: string;
|
||||||
@ -37,7 +37,6 @@ function Markdown({ children, className }: MarkdownProps) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.querySelectorAll(".file-instance").forEach((el) => {
|
document.querySelectorAll(".file-instance").forEach((el) => {
|
||||||
const parent = el.parentElement as HTMLElement;
|
const parent = el.parentElement as HTMLElement;
|
||||||
@ -76,12 +75,15 @@ function Markdown({ children, className }: MarkdownProps) {
|
|||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<div className={`markdown-syntax`}>
|
<div className={`markdown-syntax`}>
|
||||||
<div className={`markdown-syntax-header`}>
|
<div className={`markdown-syntax-header`}>
|
||||||
<Copy className={`h-3 w-3`} onClick={async () => {
|
<Copy
|
||||||
await copyClipboard(children.toString());
|
className={`h-3 w-3`}
|
||||||
toast({
|
onClick={async () => {
|
||||||
title: t("share.copied"),
|
await copyClipboard(children.toString());
|
||||||
});
|
toast({
|
||||||
}} />
|
title: t("share.copied"),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<p>{match[1]}</p>
|
<p>{match[1]}</p>
|
||||||
</div>
|
</div>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Message } from "../conversation/types.ts";
|
import { Message } from "@/conversation/types.ts";
|
||||||
import Markdown from "./Markdown.tsx";
|
import Markdown from "@/components/Markdown.tsx";
|
||||||
import {
|
import {
|
||||||
Cloud,
|
Cloud,
|
||||||
CloudFog,
|
CloudFog,
|
||||||
@ -8,28 +8,30 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
MousePointerSquare,
|
MousePointerSquare,
|
||||||
Power,
|
Power,
|
||||||
RotateCcw, ScanText,
|
RotateCcw,
|
||||||
|
ScanText,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from "./ui/context-menu.tsx";
|
} from "@/components/ui/context-menu.tsx";
|
||||||
|
import { filterMessage } from "@/utils/processor.ts";
|
||||||
import {
|
import {
|
||||||
copyClipboard,
|
copyClipboard,
|
||||||
filterMessage, getSelectionTextInArea,
|
|
||||||
saveAsFile,
|
saveAsFile,
|
||||||
|
getSelectionTextInArea,
|
||||||
useInputValue,
|
useInputValue,
|
||||||
} from "../utils.ts";
|
} from "@/utils/dom.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "./ui/tooltip.tsx";
|
} from "@/components/ui/tooltip.tsx";
|
||||||
import {Ref, useRef, useState} from "react";
|
import { Ref, useRef, useState } from "react";
|
||||||
|
|
||||||
type MessageProps = {
|
type MessageProps = {
|
||||||
message: Message;
|
message: Message;
|
||||||
@ -39,7 +41,7 @@ type MessageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function MessageSegment(props: MessageProps) {
|
function MessageSegment(props: MessageProps) {
|
||||||
const [ copied, setCopied ] = useState<string>("");
|
const [copied, setCopied] = useState<string>("");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const { message } = props;
|
const { message } = props;
|
||||||
@ -74,15 +76,11 @@ function MessageSegment(props: MessageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
{
|
{copied.length > 0 && (
|
||||||
copied.length > 0 && (
|
<ContextMenuItem onClick={() => copyClipboard(copied)}>
|
||||||
<ContextMenuItem
|
<ScanText className={`h-4 w-4 mr-2`} /> {t("message.copy-area")}
|
||||||
onClick={() => copyClipboard(copied)}
|
</ContextMenuItem>
|
||||||
>
|
)}
|
||||||
<ScanText className={`h-4 w-4 mr-2`} /> {t("message.copy-area")}
|
|
||||||
</ContextMenuItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => copyClipboard(filterMessage(message.content))}
|
onClick={() => copyClipboard(filterMessage(message.content))}
|
||||||
>
|
>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Button } from "./ui/button.tsx";
|
import { Button } from "./ui/button.tsx";
|
||||||
import { selectMessages } from "../store/chat.ts";
|
import { selectMessages } from "@/store/chat.ts";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { MessageSquarePlus } from "lucide-react";
|
import { MessageSquarePlus } from "lucide-react";
|
||||||
import { toggleConversation } from "../conversation/history.ts";
|
import { toggleConversation } from "@/conversation/history.ts";
|
||||||
|
|
||||||
function ProjectLink() {
|
function ProjectLink() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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";
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "./ui/select";
|
} from "./ui/select";
|
||||||
import { mobile } from "../utils.ts";
|
import { mobile } from "@/utils/device.ts";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Badge } from "./ui/badge.tsx";
|
import { Badge } from "./ui/badge.tsx";
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import NavBar from "./NavBar.tsx";
|
import NavBar from "./NavBar.tsx";
|
||||||
import { ThemeProvider } from "../ThemeProvider.tsx";
|
import { ThemeProvider } from "@/components/ThemeProvider.tsx";
|
||||||
import DialogManager from "../../dialogs";
|
import DialogManager from "@/dialogs";
|
||||||
|
|
||||||
function AppProvider() {
|
function AppProvider() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { logout, selectUsername } from "../../store/auth.ts";
|
import { logout, selectUsername } from "@/store/auth.ts";
|
||||||
import {
|
import { openDialog as openQuotaDialog, quotaSelector } from "@/store/quota.ts";
|
||||||
openDialog as openQuotaDialog,
|
|
||||||
quotaSelector,
|
|
||||||
} from "../../store/quota.ts";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -12,8 +9,8 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu.tsx";
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { Button } from "../ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import {
|
import {
|
||||||
BadgeCent,
|
BadgeCent,
|
||||||
Boxes,
|
Boxes,
|
||||||
@ -23,11 +20,11 @@ import {
|
|||||||
ListStart,
|
ListStart,
|
||||||
Plug,
|
Plug,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { openDialog as openSub } from "../../store/subscription.ts";
|
import { openDialog as openSub } from "@/store/subscription.ts";
|
||||||
import { openDialog as openPackageDialog } from "../../store/package.ts";
|
import { openDialog as openPackageDialog } from "@/store/package.ts";
|
||||||
import { openDialog as openInvitationDialog } from "../../store/invitation.ts";
|
import { openDialog as openInvitationDialog } from "@/store/invitation.ts";
|
||||||
import { openDialog as openSharingDialog } from "../../store/sharing.ts";
|
import { openDialog as openSharingDialog } from "@/store/sharing.ts";
|
||||||
import { openDialog as openApiDialog } from "../../store/api.ts";
|
import { openDialog as openApiDialog } from "@/store/api.ts";
|
||||||
|
|
||||||
type MenuBarProps = {
|
type MenuBarProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -5,16 +5,16 @@ import {
|
|||||||
selectAuthenticated,
|
selectAuthenticated,
|
||||||
selectUsername,
|
selectUsername,
|
||||||
validateToken,
|
validateToken,
|
||||||
} from "../../store/auth.ts";
|
} from "@/store/auth.ts";
|
||||||
import { Button } from "../ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { Menu } from "lucide-react";
|
import { Menu } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { login, tokenField } from "../../conf.ts";
|
import { login, tokenField } from "@/conf.ts";
|
||||||
import { toggleMenu } from "../../store/menu.ts";
|
import { toggleMenu } from "@/store/menu.ts";
|
||||||
import ProjectLink from "../ProjectLink.tsx";
|
import ProjectLink from "@/components/ProjectLink.tsx";
|
||||||
import ModeToggle from "../ThemeProvider.tsx";
|
import ModeToggle from "@/components/ThemeProvider.tsx";
|
||||||
import I18nProvider from "../I18nProvider.tsx";
|
import I18nProvider from "@/components/I18nProvider.tsx";
|
||||||
import router from "../../router.tsx";
|
import router from "@/router.tsx";
|
||||||
import MenuBar from "./MenuBar.tsx";
|
import MenuBar from "./MenuBar.tsx";
|
||||||
|
|
||||||
function NavMenu() {
|
function NavMenu() {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Message } from "../../conversation/types.ts";
|
import { Message } from "@/conversation/types.ts";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { selectCurrent, selectMessages } from "../../store/chat.ts";
|
import { selectCurrent, selectMessages } from "@/store/chat.ts";
|
||||||
import { Button } from "../ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import MessageSegment from "../Message.tsx";
|
import MessageSegment from "@/components/Message.tsx";
|
||||||
import { connectionEvent } from "../../events/connection.ts";
|
import { connectionEvent } from "@/events/connection.ts";
|
||||||
|
|
||||||
function ChatInterface() {
|
function ChatInterface() {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import FileProvider, { FileObject } from "../FileProvider.tsx";
|
import FileProvider, { FileObject } from "@/components/FileProvider.tsx";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { selectAuthenticated, selectInit } from "../../store/auth.ts";
|
import { selectAuthenticated, selectInit } from "@/store/auth.ts";
|
||||||
import {
|
import {
|
||||||
selectMessages,
|
selectMessages,
|
||||||
selectModel,
|
selectModel,
|
||||||
selectWeb,
|
selectWeb,
|
||||||
setWeb,
|
setWeb,
|
||||||
} from "../../store/chat.ts";
|
} from "@/store/chat.ts";
|
||||||
import { manager } from "../../conversation/manager.ts";
|
import { manager } from "@/conversation/manager.ts";
|
||||||
import { formatMessage, triggerInstallApp } from "../../utils.ts";
|
import { formatMessage } from "@/utils/processor.ts";
|
||||||
import ChatInterface from "./ChatInterface.tsx";
|
import { triggerInstallApp } from "@/utils/app.ts";
|
||||||
import { Button } from "../ui/button.tsx";
|
import ChatInterface from "@/components/home/ChatInterface.tsx";
|
||||||
import router from "../../router.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
|
import router from "@/router.tsx";
|
||||||
import {
|
import {
|
||||||
BookMarked,
|
BookMarked,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@ -26,10 +27,10 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../ui/tooltip.tsx";
|
} from "@/components/ui/tooltip.tsx";
|
||||||
import { Toggle } from "../ui/toggle.tsx";
|
import { Toggle } from "@/components/ui/toggle.tsx";
|
||||||
import { Input } from "../ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import EditorProvider from "../EditorProvider.tsx";
|
import EditorProvider from "@/components/EditorProvider.tsx";
|
||||||
import ModelSelector from "./ModelSelector.tsx";
|
import ModelSelector from "./ModelSelector.tsx";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -37,8 +38,9 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { version } from "../../conf.ts";
|
import { version } from "@/conf.ts";
|
||||||
|
import { getQueryParam } from "@/utils/path.ts";
|
||||||
|
|
||||||
function ChatSpace() {
|
function ChatSpace() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@ -145,8 +147,7 @@ function ChatWrapper() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!init) return;
|
if (!init) return;
|
||||||
const search = new URLSearchParams(window.location.search);
|
const query = getQueryParam("q").trim();
|
||||||
const query = (search.get("q") || "").trim();
|
|
||||||
if (query.length > 0) processSend(query, auth, model, web).then();
|
if (query.length > 0) processSend(query, auth, model, web).then();
|
||||||
window.history.replaceState({}, "", "/");
|
window.history.replaceState({}, "", "/");
|
||||||
}, [init]);
|
}, [init]);
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { toggleConversation } from "../../conversation/history.ts";
|
import { toggleConversation } from "@/conversation/history.ts";
|
||||||
import { filterMessage, mobile } from "../../utils.ts";
|
import { mobile } from "@/utils/device.ts";
|
||||||
import { setMenu } from "../../store/menu.ts";
|
import { filterMessage } from "@/utils/processor.ts";
|
||||||
|
import { setMenu } from "@/store/menu.ts";
|
||||||
import { MessageSquare, MoreHorizontal, Share2, Trash2 } from "lucide-react";
|
import { MessageSquare, MoreHorizontal, Share2, Trash2 } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu.tsx";
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ConversationInstance } from "../../conversation/types.ts";
|
import { ConversationInstance } from "@/conversation/types.ts";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
type ConversationSegmentProps = {
|
type ConversationSegmentProps = {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import SelectGroup, { SelectItemProps } from "../SelectGroup.tsx";
|
import SelectGroup, { SelectItemProps } from "@/components/SelectGroup.tsx";
|
||||||
import { supportModels } from "../../conf.ts";
|
import { login, supportModels } from "@/conf.ts";
|
||||||
import { selectModel, setModel } from "../../store/chat.ts";
|
import { selectModel, setModel } from "@/store/chat.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { selectAuthenticated } from "../../store/auth.ts";
|
import { selectAuthenticated } from "@/store/auth.ts";
|
||||||
import { useToast } from "../ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Model } from "../../conversation/types.ts";
|
import { Model } from "@/conversation/types.ts";
|
||||||
import { modelEvent } from "../../events/model.ts";
|
import { modelEvent } from "@/events/model.ts";
|
||||||
import { isSubscribedSelector } from "../../store/subscription.ts";
|
import { isSubscribedSelector } from "@/store/subscription.ts";
|
||||||
import {teenagerSelector} from "../../store/package.ts";
|
import { teenagerSelector } from "@/store/package.ts";
|
||||||
|
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||||
|
|
||||||
function GetModel(name: string): Model {
|
function GetModel(name: string): Model {
|
||||||
return supportModels.find((model) => model.id === name) as Model;
|
return supportModels.find((model) => model.id === name) as Model;
|
||||||
@ -42,30 +43,28 @@ function ModelSelector(props: ModelSelectorProps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = supportModels.map(
|
const list = supportModels.map((model: Model): SelectItemProps => {
|
||||||
(model: Model): SelectItemProps => {
|
const array = ["gpt-4", "claude-2"];
|
||||||
const array = ["gpt-4", "claude-2"];
|
if (subscription && array.includes(model.id)) {
|
||||||
if (subscription && array.includes(model.id)) {
|
|
||||||
return {
|
|
||||||
name: model.id,
|
|
||||||
value: model.name,
|
|
||||||
badge: { variant: "gold", name: "plus" },
|
|
||||||
} as SelectItemProps;
|
|
||||||
} else if (student && model.id === "claude-2") {
|
|
||||||
return {
|
|
||||||
name: model.id,
|
|
||||||
value: model.name,
|
|
||||||
badge: { variant: "gold", name: "student" },
|
|
||||||
} as SelectItemProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
name: model.id,
|
||||||
|
value: model.name,
|
||||||
|
badge: { variant: "gold", name: "plus" },
|
||||||
|
} as SelectItemProps;
|
||||||
|
} else if (student && model.id === "claude-2") {
|
||||||
|
return {
|
||||||
|
name: model.id,
|
||||||
|
value: model.name,
|
||||||
|
badge: { variant: "gold", name: "student" },
|
||||||
|
} as SelectItemProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
name: model.id,
|
name: model.id,
|
||||||
value: model.name,
|
value: model.name,
|
||||||
badge: model.free && { variant: "default", name: "free" }
|
badge: model.free && { variant: "default", name: "free" },
|
||||||
} as SelectItemProps;
|
} as SelectItemProps;
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectGroup
|
<SelectGroup
|
||||||
@ -81,6 +80,11 @@ function ModelSelector(props: ModelSelectorProps) {
|
|||||||
if (!auth && model.auth) {
|
if (!auth && model.auth) {
|
||||||
toast({
|
toast({
|
||||||
title: t("login-require"),
|
title: t("login-require"),
|
||||||
|
action: (
|
||||||
|
<ToastAction altText={t("login")} onClick={login}>
|
||||||
|
{t("login")}
|
||||||
|
</ToastAction>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,23 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../store";
|
import { RootState } from "@/store";
|
||||||
import { selectAuthenticated, selectUsername } from "../../store/auth.ts";
|
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
|
||||||
import { selectCurrent, selectHistory } from "../../store/chat.ts";
|
import { selectCurrent, selectHistory } from "@/store/chat.ts";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { ConversationInstance } from "../../conversation/types.ts";
|
import { ConversationInstance } from "@/conversation/types.ts";
|
||||||
import { useToast } from "../ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import {
|
import { extractMessage, filterMessage } from "@/utils/processor.ts";
|
||||||
copyClipboard,
|
import { copyClipboard } from "@/utils/dom.ts";
|
||||||
extractMessage,
|
import { useEffectAsync, useAnimation } from "@/utils/hook.ts";
|
||||||
filterMessage,
|
import { mobile } from "@/utils/device.ts";
|
||||||
mobile,
|
|
||||||
useAnimation,
|
|
||||||
useEffectAsync,
|
|
||||||
} from "../../utils.ts";
|
|
||||||
import {
|
import {
|
||||||
deleteAllConversations,
|
deleteAllConversations,
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
toggleConversation,
|
toggleConversation,
|
||||||
updateConversationList,
|
updateConversationList,
|
||||||
} from "../../conversation/history.ts";
|
} from "@/conversation/history.ts";
|
||||||
import { Button } from "../ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { setMenu } from "../../store/menu.ts";
|
import { setMenu } from "@/store/menu.ts";
|
||||||
import {
|
import {
|
||||||
Copy,
|
Copy,
|
||||||
Eraser,
|
Eraser,
|
||||||
@ -41,15 +37,12 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "../ui/alert-dialog.tsx";
|
} from "@/components/ui/alert-dialog.tsx";
|
||||||
import {
|
import { getSharedLink, shareConversation } from "@/conversation/sharing.ts";
|
||||||
getSharedLink,
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
shareConversation,
|
import { login } from "@/conf.ts";
|
||||||
} from "../../conversation/sharing.ts";
|
import MenuBar from "@/components/app/MenuBar.tsx";
|
||||||
import { Input } from "../ui/input.tsx";
|
import { Separator } from "@/components/ui/separator.tsx";
|
||||||
import { login } from "../../conf.ts";
|
|
||||||
import MenuBar from "../app/MenuBar.tsx";
|
|
||||||
import { Separator } from "../ui/separator.tsx";
|
|
||||||
|
|
||||||
type Operation = {
|
type Operation = {
|
||||||
target: ConversationInstance | null;
|
target: ConversationInstance | null;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { File } from "lucide-react";
|
import { File } from "lucide-react";
|
||||||
import { saveAsFile } from "../../utils.ts";
|
import { saveAsFile } from "@/utils/dom.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* file format:
|
* file format:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { tokenField, ws_api } from "../conf.ts";
|
import { tokenField, ws_api } from "@/conf.ts";
|
||||||
|
|
||||||
export const endpoint = `${ws_api}/chat`;
|
export const endpoint = `${ws_api}/chat`;
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
||||||
import { Message } from "./types.ts";
|
import { Message } from "./types.ts";
|
||||||
import { sharingEvent } from "../events/sharing.ts";
|
import { sharingEvent } from "@/events/sharing.ts";
|
||||||
import { connectionEvent } from "../events/connection.ts";
|
import { connectionEvent } from "@/events/connection.ts";
|
||||||
import { AppDispatch } from "../store";
|
import { AppDispatch } from "@/store";
|
||||||
import { setMessages } from "../store/chat.ts";
|
import { setMessages } from "@/store/chat.ts";
|
||||||
import { modelEvent } from "../events/model.ts";
|
import { modelEvent } from "@/events/model.ts";
|
||||||
|
|
||||||
type ConversationCallback = (idx: number, message: Message[]) => boolean;
|
type ConversationCallback = (idx: number, message: Message[]) => boolean;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ws_api } from "../conf.ts";
|
import { ws_api } from "@/conf.ts";
|
||||||
|
|
||||||
export const endpoint = `${ws_api}/generation/create`;
|
export const endpoint = `${ws_api}/generation/create`;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import type { ConversationInstance } from "./types.ts";
|
import type { ConversationInstance } from "./types.ts";
|
||||||
import { setHistory } from "../store/chat.ts";
|
import { setHistory } from "@/store/chat.ts";
|
||||||
import { manager } from "./manager.ts";
|
import { manager } from "./manager.ts";
|
||||||
import { AppDispatch } from "../store";
|
import { AppDispatch } from "@/store";
|
||||||
|
|
||||||
export async function updateConversationList(
|
export async function updateConversationList(
|
||||||
dispatch: AppDispatch,
|
dispatch: AppDispatch,
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { Conversation } from "./conversation";
|
import { Conversation } from "@/conversation/conversation.ts";
|
||||||
import { ConversationMapper, Message } from "./types.ts";
|
import { ConversationMapper, Message } from "@/conversation/types.ts";
|
||||||
import { loadConversation } from "./history.ts";
|
import { loadConversation } from "@/conversation/history.ts";
|
||||||
import {
|
import {
|
||||||
addHistory,
|
addHistory,
|
||||||
removeHistory,
|
removeHistory,
|
||||||
setCurrent,
|
setCurrent,
|
||||||
setMessages,
|
setMessages,
|
||||||
} from "../store/chat.ts";
|
} from "@/store/chat.ts";
|
||||||
import { useShared } from "../utils.ts";
|
import { useShared } from "@/utils/hook.ts";
|
||||||
import { ChatProps } from "./connection.ts";
|
import { ChatProps } from "@/conversation/connection.ts";
|
||||||
import { AppDispatch } from "../store";
|
import { AppDispatch } from "@/store";
|
||||||
import { sharingEvent } from "../events/sharing.ts";
|
import { sharingEvent } from "@/events/sharing.ts";
|
||||||
|
|
||||||
export class Manager {
|
export class Manager {
|
||||||
conversations: Record<number, Conversation>;
|
conversations: Record<number, Conversation>;
|
||||||
|
@ -5,9 +5,9 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import "../assets/api.less";
|
import "@/assets/api.less";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
@ -16,12 +16,13 @@ import {
|
|||||||
setDialog,
|
setDialog,
|
||||||
keySelector,
|
keySelector,
|
||||||
getApiKey,
|
getApiKey,
|
||||||
} from "../store/api.ts";
|
} from "@/store/api.ts";
|
||||||
import { Input } from "../components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { Copy, ExternalLink } from "lucide-react";
|
import { Copy, ExternalLink } from "lucide-react";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { copyClipboard, useEffectAsync } from "../utils.ts";
|
import { copyClipboard } from "@/utils/dom.ts";
|
||||||
import { selectInit } from "../store/auth.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
|
import { selectInit } from "@/store/auth.ts";
|
||||||
|
|
||||||
function ApiKey() {
|
function ApiKey() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -5,15 +5,15 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { closeDialog, dialogSelector, setDialog } from "../store/invitation.ts";
|
import { closeDialog, dialogSelector, setDialog } from "@/store/invitation.ts";
|
||||||
import { Input } from "../components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { getInvitation } from "../conversation/invitation.ts";
|
import { getInvitation } from "@/conversation/invitation.ts";
|
||||||
|
|
||||||
function Invitation() {
|
function Invitation() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -5,9 +5,9 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import "../assets/package.less";
|
import "@/assets/package.less";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
@ -17,11 +17,11 @@ import {
|
|||||||
refreshPackageTask,
|
refreshPackageTask,
|
||||||
setDialog,
|
setDialog,
|
||||||
teenagerSelector,
|
teenagerSelector,
|
||||||
} from "../store/package.ts";
|
} from "@/store/package.ts";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Gift } from "lucide-react";
|
import { Gift } from "lucide-react";
|
||||||
import { Separator } from "../components/ui/separator.tsx";
|
import { Separator } from "@/components/ui/separator.tsx";
|
||||||
import { Badge } from "../components/ui/badge.tsx";
|
import { Badge } from "@/components/ui/badge.tsx";
|
||||||
|
|
||||||
function Package() {
|
function Package() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
dialogSelector,
|
dialogSelector,
|
||||||
refreshQuotaTask,
|
refreshQuotaTask,
|
||||||
setDialog,
|
setDialog,
|
||||||
} from "../store/quota.ts";
|
} from "@/store/quota.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@ -13,8 +13,8 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import "../assets/quota.less";
|
import "@/assets/quota.less";
|
||||||
import {
|
import {
|
||||||
BadgePercent,
|
BadgePercent,
|
||||||
Cloud,
|
Cloud,
|
||||||
@ -24,10 +24,10 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
Plus,
|
Plus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Input } from "../components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { testNumberInputEvent } from "../utils.ts";
|
import { testNumberInputEvent } from "@/utils/dom.ts";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { Separator } from "../components/ui/separator.tsx";
|
import { Separator } from "@/components/ui/separator.tsx";
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -37,10 +37,10 @@ import {
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "../components/ui/alert-dialog.tsx";
|
} from "@/components/ui/alert-dialog.tsx";
|
||||||
import { AlertDialogTitle } from "@radix-ui/react-alert-dialog";
|
import { AlertDialogTitle } from "@radix-ui/react-alert-dialog";
|
||||||
import { buyQuota } from "../conversation/addition.ts";
|
import { buyQuota } from "@/conversation/addition.ts";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
|
|
||||||
type AmountComponentProps = {
|
type AmountComponentProps = {
|
||||||
amount: number;
|
amount: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import "../assets/share-manager.less";
|
import "@/assets/share-manager.less";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
@ -6,10 +6,10 @@ import {
|
|||||||
dataSelector,
|
dataSelector,
|
||||||
syncData,
|
syncData,
|
||||||
deleteData,
|
deleteData,
|
||||||
} from "../store/sharing.ts";
|
} from "@/store/sharing.ts";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { selectAuthenticated, selectInit } from "../store/auth.ts";
|
import { selectAuthenticated, selectInit } from "@/store/auth.ts";
|
||||||
import { useEffectAsync } from "../utils.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@ -25,9 +25,9 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../components/ui/table.tsx";
|
} from "@/components/ui/table.tsx";
|
||||||
import { closeDialog, setDialog } from "../store/sharing.ts";
|
import { closeDialog, setDialog } from "@/store/sharing.ts";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Eye, MoreHorizontal, Trash2 } from "lucide-react";
|
import { Eye, MoreHorizontal, Trash2 } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
@ -35,8 +35,8 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../components/ui/dropdown-menu.tsx";
|
} from "@/components/ui/dropdown-menu.tsx";
|
||||||
import { getSharedLink, SharingPreviewForm } from "../conversation/sharing.ts";
|
import { getSharedLink, SharingPreviewForm } from "@/conversation/sharing.ts";
|
||||||
|
|
||||||
type ShareTableProps = {
|
type ShareTableProps = {
|
||||||
data: SharingPreviewForm[];
|
data: SharingPreviewForm[];
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
refreshSubscriptionTask,
|
refreshSubscriptionTask,
|
||||||
setDialog,
|
setDialog,
|
||||||
usageSelector,
|
usageSelector,
|
||||||
} from "../store/subscription.ts";
|
} from "@/store/subscription.ts";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -15,12 +15,12 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../components/ui/dialog.tsx";
|
} from "@/components/ui/dialog.tsx";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import "../assets/subscription.less";
|
import "@/assets/subscription.less";
|
||||||
import {
|
import {
|
||||||
BookText,
|
BookText,
|
||||||
Calendar,
|
Calendar,
|
||||||
@ -35,16 +35,16 @@ import {
|
|||||||
ServerCrash,
|
ServerCrash,
|
||||||
Webhook,
|
Webhook,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../components/ui/select.tsx";
|
} from "@/components/ui/select.tsx";
|
||||||
import { Badge } from "../components/ui/badge.tsx";
|
import { Badge } from "@/components/ui/badge.tsx";
|
||||||
import { buySubscription } from "../conversation/addition.ts";
|
import { buySubscription } from "@/conversation/addition.ts";
|
||||||
|
|
||||||
function calc_prize(month: number): number {
|
function calc_prize(month: number): number {
|
||||||
const base = 32 * month;
|
const base = 32 * month;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Toaster } from "../components/ui/toaster.tsx";
|
import { Toaster } from "@/components/ui/toaster.tsx";
|
||||||
import Quota from "./Quota.tsx";
|
import Quota from "./Quota.tsx";
|
||||||
import ApiKey from "./ApiKey.tsx";
|
import ApiKey from "./ApiKey.tsx";
|
||||||
import Package from "./Package.tsx";
|
import Package from "./Package.tsx";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EventCommitter } from "./struct.ts";
|
import { EventCommitter } from "./struct.ts";
|
||||||
import { Message } from "../conversation/types.ts";
|
import { Message } from "@/conversation/types.ts";
|
||||||
|
|
||||||
export type SharingEvent = {
|
export type SharingEvent = {
|
||||||
refer: string;
|
refer: string;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { ToastAction } from "../components/ui/toast.tsx";
|
import { ToastAction } from "@/components/ui/toast.tsx";
|
||||||
import { login } from "../conf.ts";
|
import { login } from "@/conf.ts";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import Loader from "../components/Loader.tsx";
|
import Loader from "@/components/Loader.tsx";
|
||||||
import "../assets/auth.less";
|
import "@/assets/auth.less";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { validateToken } from "../store/auth.ts";
|
import { validateToken } from "@/store/auth.ts";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import router from "../router.tsx";
|
import router from "@/router.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function Auth() {
|
function Auth() {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import "../assets/generation.less";
|
import "@/assets/generation.less";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { ChevronLeft, Cloud, FileDown, Send } from "lucide-react";
|
import { ChevronLeft, Cloud, FileDown, Send } from "lucide-react";
|
||||||
import { rest_api } from "../conf.ts";
|
import { rest_api } from "@/conf.ts";
|
||||||
import router from "../router.tsx";
|
import router from "@/router.tsx";
|
||||||
import { Input } from "../components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { manager } from "../conversation/generation.ts";
|
import { manager } from "@/conversation/generation.ts";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { handleGenerationData } from "../utils.ts";
|
import { handleGenerationData } from "@/utils/processor.ts";
|
||||||
import { selectModel } from "../store/chat.ts";
|
import { selectModel } from "@/store/chat.ts";
|
||||||
import ModelSelector from "../components/home/ModelSelector.tsx";
|
import ModelSelector from "@/components/home/ModelSelector.tsx";
|
||||||
|
|
||||||
type WrapperProps = {
|
type WrapperProps = {
|
||||||
onSend?: (value: string, model: string) => boolean;
|
onSend?: (value: string, model: string) => boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "../assets/home.less";
|
import "@/assets/home.less";
|
||||||
import "../assets/chat.less";
|
import "@/assets/chat.less";
|
||||||
import ChatWrapper from "../components/home/ChatWrapper.tsx";
|
import ChatWrapper from "@/components/home/ChatWrapper.tsx";
|
||||||
import SideBar from "../components/home/SideBar.tsx";
|
import SideBar from "@/components/home/SideBar.tsx";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "../assets/404.less";
|
import "@/assets/404.less";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { HelpCircle } from "lucide-react";
|
import { HelpCircle } from "lucide-react";
|
||||||
import router from "../router.tsx";
|
import router from "@/router.tsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function NotFound() {
|
function NotFound() {
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import "../assets/sharing.less";
|
import "@/assets/sharing.less";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
viewConversation,
|
viewConversation,
|
||||||
ViewData,
|
ViewData,
|
||||||
ViewForm,
|
ViewForm,
|
||||||
} from "../conversation/sharing.ts";
|
} from "@/conversation/sharing.ts";
|
||||||
import { copyClipboard, saveAsFile, useEffectAsync } from "../utils.ts";
|
import { copyClipboard, saveAsFile } from "@/utils/dom.ts";
|
||||||
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Copy, File, HelpCircle, Loader2, MessagesSquare } from "lucide-react";
|
import { Copy, File, HelpCircle, Loader2, MessagesSquare } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import MessageSegment from "../components/Message.tsx";
|
import MessageSegment from "@/components/Message.tsx";
|
||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import router from "../router.tsx";
|
import router from "@/router.tsx";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "@/components/ui/use-toast.ts";
|
||||||
import { sharingEvent } from "../events/sharing.ts";
|
import { sharingEvent } from "@/events/sharing.ts";
|
||||||
import { Message } from "../conversation/types.ts";
|
import { Message } from "@/conversation/types.ts";
|
||||||
|
|
||||||
type SharingFormProps = {
|
type SharingFormProps = {
|
||||||
refer?: string;
|
refer?: string;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { getKey } from "../conversation/addition.ts";
|
import { getKey } from "@/conversation/addition.ts";
|
||||||
import { AppDispatch, RootState } from "./index.ts";
|
import { AppDispatch, RootState } from "./index.ts";
|
||||||
|
|
||||||
export const apiSlice = createSlice({
|
export const apiSlice = createSlice({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { tokenField } from "../conf.ts";
|
import { tokenField } from "@/conf.ts";
|
||||||
import { AppDispatch } from "./index.ts";
|
import { AppDispatch } from "./index.ts";
|
||||||
|
|
||||||
export const authSlice = createSlice({
|
export const authSlice = createSlice({
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { ConversationInstance, Model } from "../conversation/types.ts";
|
import { ConversationInstance, Model } from "@/conversation/types.ts";
|
||||||
import { Message } from "../conversation/types.ts";
|
import { Message } from "@/conversation/types.ts";
|
||||||
import { insertStart } from "../utils.ts";
|
import { insertStart } from "@/utils/base.ts";
|
||||||
import { RootState } from "./index.ts";
|
import { RootState } from "./index.ts";
|
||||||
import { supportModels } from "../conf.ts";
|
import { supportModels } from "@/conf.ts";
|
||||||
|
|
||||||
type initialStateType = {
|
type initialStateType = {
|
||||||
history: ConversationInstance[];
|
history: ConversationInstance[];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { mobile } from "../utils.ts";
|
import { mobile } from "@/utils/device.ts";
|
||||||
|
|
||||||
export const menuSlice = createSlice({
|
export const menuSlice = createSlice({
|
||||||
name: "menu",
|
name: "menu",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { getPackage } from "../conversation/addition.ts";
|
import { getPackage } from "@/conversation/addition.ts";
|
||||||
import { AppDispatch } from "./index.ts";
|
import { AppDispatch } from "./index.ts";
|
||||||
|
|
||||||
export const packageSlice = createSlice({
|
export const packageSlice = createSlice({
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
deleteSharing,
|
deleteSharing,
|
||||||
listSharing,
|
listSharing,
|
||||||
SharingPreviewForm,
|
SharingPreviewForm,
|
||||||
} from "../conversation/sharing.ts";
|
} from "@/conversation/sharing.ts";
|
||||||
|
|
||||||
export const sharingSlice = createSlice({
|
export const sharingSlice = createSlice({
|
||||||
name: "sharing",
|
name: "sharing",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { getSubscription } from "../conversation/addition.ts";
|
import { getSubscription } from "@/conversation/addition.ts";
|
||||||
import { AppDispatch } from "./index.ts";
|
import { AppDispatch } from "./index.ts";
|
||||||
|
|
||||||
export const subscriptionSlice = createSlice({
|
export const subscriptionSlice = createSlice({
|
||||||
|
284
app/src/utils.ts
284
app/src/utils.ts
@ -1,284 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { FileObject } from "./components/FileProvider.tsx";
|
|
||||||
|
|
||||||
export let event: BeforeInstallPromptEvent | undefined;
|
|
||||||
export let mobile = isMobile();
|
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
mobile = isMobile();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("beforeinstallprompt", (e: Event) => {
|
|
||||||
// e.preventDefault();
|
|
||||||
event = e as BeforeInstallPromptEvent;
|
|
||||||
});
|
|
||||||
|
|
||||||
export function triggerInstallApp() {
|
|
||||||
if (!event) return;
|
|
||||||
try {
|
|
||||||
event.prompt();
|
|
||||||
event.userChoice.then((choice: any) => {
|
|
||||||
console.debug(`[service] installed app (status: ${choice.outcome})`);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.debug("[service] install app error", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
event = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isMobile(): boolean {
|
|
||||||
return (
|
|
||||||
(document.documentElement.clientWidth || window.innerWidth) <= 668 ||
|
|
||||||
(document.documentElement.clientHeight || window.innerHeight) <= 468 ||
|
|
||||||
navigator.userAgent.includes("Mobile")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useEffectAsync<T>(effect: () => Promise<T>, deps?: any[]) {
|
|
||||||
return useEffect(() => {
|
|
||||||
effect().catch((err) =>
|
|
||||||
console.debug("[runtime] error during use effect", err),
|
|
||||||
);
|
|
||||||
}, deps);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAnimation(
|
|
||||||
ref: React.MutableRefObject<any>,
|
|
||||||
cls: string,
|
|
||||||
min?: number,
|
|
||||||
): (() => number) | undefined {
|
|
||||||
if (!ref.current) return;
|
|
||||||
const target = ref.current as HTMLButtonElement;
|
|
||||||
const stamp = Date.now();
|
|
||||||
target.classList.add(cls);
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
const duration = Date.now() - stamp;
|
|
||||||
const timeout = min ? Math.max(min - duration, 0) : 0;
|
|
||||||
setTimeout(() => target.classList.remove(cls), timeout);
|
|
||||||
return timeout;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useShared<T>(): {
|
|
||||||
hook: (v: T) => void;
|
|
||||||
useHook: () => Promise<T>;
|
|
||||||
} {
|
|
||||||
let value: T | undefined = undefined;
|
|
||||||
return {
|
|
||||||
hook: (v: T) => {
|
|
||||||
value = v;
|
|
||||||
},
|
|
||||||
useHook: () => {
|
|
||||||
return new Promise<T>((resolve) => {
|
|
||||||
if (value) return resolve(value);
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
if (value) {
|
|
||||||
clearInterval(interval);
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function insert<T>(arr: T[], idx: number, value: T): T[] {
|
|
||||||
return [...arr.slice(0, idx), value, ...arr.slice(idx)];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function insertStart<T>(arr: T[], value: T): T[] {
|
|
||||||
return [value, ...arr];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function remove<T>(arr: T[], idx: number): T[] {
|
|
||||||
return [...arr.slice(0, idx), ...arr.slice(idx + 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replace<T>(arr: T[], idx: number, value: T): T[] {
|
|
||||||
return [...arr.slice(0, idx), value, ...arr.slice(idx + 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function move<T>(arr: T[], from: number, to: number): T[] {
|
|
||||||
const value = arr[from];
|
|
||||||
return insert(remove(arr, from), to, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function copyClipboard(text: string) {
|
|
||||||
if (!navigator.clipboard) {
|
|
||||||
const input = document.createElement("input");
|
|
||||||
input.value = text;
|
|
||||||
input.style.position = "absolute";
|
|
||||||
input.style.left = "-9999px";
|
|
||||||
document.body.appendChild(input);
|
|
||||||
input.select();
|
|
||||||
document.execCommand("copy");
|
|
||||||
document.body.removeChild(input);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQueryParams() {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const obj: Record<string, string> = {};
|
|
||||||
for (const [key, value] of params.entries()) {
|
|
||||||
obj[key] = value;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQueryParam(key: string): string {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
return params.get(key) || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveAsFile(filename: string, content: string) {
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = URL.createObjectURL(new Blob([content]));
|
|
||||||
a.download = filename;
|
|
||||||
a.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replaceInputValue(
|
|
||||||
input: HTMLInputElement | undefined,
|
|
||||||
value: string,
|
|
||||||
) {
|
|
||||||
return input && (input.value = value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInputValue(id: string, value: string) {
|
|
||||||
const input = document.getElementById(id) as HTMLInputElement | undefined;
|
|
||||||
return input && replaceInputValue(input, value) && input.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function testNumberInputEvent(e: any): boolean {
|
|
||||||
if (
|
|
||||||
/^[0-9]+$/.test(e.key) ||
|
|
||||||
["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(e.key)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatMessage(file: FileObject, message: string): string {
|
|
||||||
message = message.trim();
|
|
||||||
if (file.name.length > 0 || file.content.length > 0) {
|
|
||||||
return `
|
|
||||||
\`\`\`file
|
|
||||||
[[${file.name}]]
|
|
||||||
${file.content}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
${message}`;
|
|
||||||
} else {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterMessage(message: string): string {
|
|
||||||
return message.replace(/```file\n\[\[.*]]\n[\s\S]*?\n```\n\n/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractMessage(
|
|
||||||
message: string,
|
|
||||||
length: number = 50,
|
|
||||||
flow: string = "...",
|
|
||||||
) {
|
|
||||||
return message.length > length ? message.slice(0, length) + flow : message;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDraggableInput(
|
|
||||||
t: any,
|
|
||||||
toast: any,
|
|
||||||
target: HTMLLabelElement,
|
|
||||||
handleChange: (filename?: string, content?: string) => void,
|
|
||||||
) {
|
|
||||||
target.addEventListener("dragover", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
target.addEventListener("drop", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const file = e.dataTransfer?.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
const data = e.target?.result as string;
|
|
||||||
if (!/^[\x00-\x7F]*$/.test(data)) {
|
|
||||||
toast({
|
|
||||||
title: t("file.parse-error"),
|
|
||||||
description: t("file.parse-error-prompt"),
|
|
||||||
});
|
|
||||||
handleChange();
|
|
||||||
} else {
|
|
||||||
handleChange(file.name, e.target?.result as string);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
} else {
|
|
||||||
handleChange();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function escapeRegExp(str: string): string {
|
|
||||||
// convert \n to [enter], \t to [tab], \r to [return], \s to [space], \" to [quote], \' to [single-quote]
|
|
||||||
return str
|
|
||||||
.replace(/\\n/g, "\n")
|
|
||||||
.replace(/\\t/g, "\t")
|
|
||||||
.replace(/\\r/g, "\r")
|
|
||||||
.replace(/\\s/g, " ")
|
|
||||||
.replace(/\\"/g, '"')
|
|
||||||
.replace(/\\'/g, "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleLine(
|
|
||||||
data: string,
|
|
||||||
max_line: number,
|
|
||||||
end?: boolean,
|
|
||||||
): string {
|
|
||||||
const segment = data.split("\n");
|
|
||||||
const line = segment.length;
|
|
||||||
if (line > max_line) {
|
|
||||||
return end ?? true
|
|
||||||
? segment.slice(line - max_line).join("\n")
|
|
||||||
: segment.slice(0, max_line).join("\n");
|
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleGenerationData(data: string): string {
|
|
||||||
data = data
|
|
||||||
.replace(/{\s*"result":\s*{/g, "")
|
|
||||||
.trim()
|
|
||||||
.replace(/}\s*$/g, "");
|
|
||||||
return handleLine(escapeRegExp(data), 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSelectionText(): string {
|
|
||||||
if (window.getSelection) {
|
|
||||||
return window.getSelection()?.toString() || "";
|
|
||||||
} else if (document.getSelection && document.getSelection()?.toString()) {
|
|
||||||
return document.getSelection()?.toString() || "";
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
29
app/src/utils/app.ts
Normal file
29
app/src/utils/app.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export let event: BeforeInstallPromptEvent | undefined;
|
||||||
|
|
||||||
|
window.addEventListener("beforeinstallprompt", (e: Event) => {
|
||||||
|
// e.preventDefault();
|
||||||
|
console.debug(`[service] catch event from app install prompt`);
|
||||||
|
event = e as BeforeInstallPromptEvent;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function triggerInstallApp() {
|
||||||
|
/**
|
||||||
|
* Trigger install app prompt
|
||||||
|
* Warning: this is a browser experimental feature, it may not work on some browsers
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* triggerInstallApp();
|
||||||
|
*/
|
||||||
|
if (!event) return;
|
||||||
|
try {
|
||||||
|
event.prompt();
|
||||||
|
event.userChoice.then((choice: any) => {
|
||||||
|
console.debug(`[service] installed app (status: ${choice.outcome})`);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.debug("[service] install app error", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
event = undefined;
|
||||||
|
}
|
20
app/src/utils/base.ts
Normal file
20
app/src/utils/base.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export function insert<T>(arr: T[], idx: number, value: T): T[] {
|
||||||
|
return [...arr.slice(0, idx), value, ...arr.slice(idx)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertStart<T>(arr: T[], value: T): T[] {
|
||||||
|
return [value, ...arr];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove<T>(arr: T[], idx: number): T[] {
|
||||||
|
return [...arr.slice(0, idx), ...arr.slice(idx + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replace<T>(arr: T[], idx: number, value: T): T[] {
|
||||||
|
return [...arr.slice(0, idx), value, ...arr.slice(idx + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function move<T>(arr: T[], from: number, to: number): T[] {
|
||||||
|
const value = arr[from];
|
||||||
|
return insert(remove(arr, from), to, value);
|
||||||
|
}
|
13
app/src/utils/device.ts
Normal file
13
app/src/utils/device.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export let mobile = isMobile();
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
mobile = isMobile();
|
||||||
|
});
|
||||||
|
|
||||||
|
export function isMobile(): boolean {
|
||||||
|
return (
|
||||||
|
(document.documentElement.clientWidth || window.innerWidth) <= 668 ||
|
||||||
|
(document.documentElement.clientHeight || window.innerHeight) <= 468 ||
|
||||||
|
navigator.userAgent.includes("Mobile")
|
||||||
|
);
|
||||||
|
}
|
177
app/src/utils/dom.ts
Normal file
177
app/src/utils/dom.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
export async function copyClipboard(text: string) {
|
||||||
|
/**
|
||||||
|
* Copy text to clipboard
|
||||||
|
* @param text Text to copy
|
||||||
|
* @example
|
||||||
|
* await copyClipboard("Hello world!");
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.value = text;
|
||||||
|
input.style.position = "absolute";
|
||||||
|
input.style.left = "-9999px";
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAsFile(filename: string, content: string) {
|
||||||
|
/**
|
||||||
|
* Save text as file
|
||||||
|
* @param filename Filename
|
||||||
|
* @param content File content
|
||||||
|
* @example
|
||||||
|
* saveAsFile("hello.txt", "Hello world!");
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
||||||
|
*/
|
||||||
|
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL(new Blob([content]));
|
||||||
|
a.download = filename;
|
||||||
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSelectionText(): string {
|
||||||
|
/**
|
||||||
|
* Get selected text
|
||||||
|
* @example
|
||||||
|
* const text = getSelectionText();
|
||||||
|
* console.log(text);
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (window.getSelection) {
|
||||||
|
return window.getSelection()?.toString() || "";
|
||||||
|
} else if (document.getSelection && document.getSelection()?.toString()) {
|
||||||
|
return document.getSelection()?.toString() || "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSelectionTextInArea(el: HTMLElement): string {
|
||||||
|
/**
|
||||||
|
* Get selected text in element
|
||||||
|
* @param el Element
|
||||||
|
* @example
|
||||||
|
* const text = getSelectionTextInArea(document.getElementById("textarea"));
|
||||||
|
* console.log(text);
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDraggableInput(
|
||||||
|
t: any,
|
||||||
|
toast: any,
|
||||||
|
target: HTMLLabelElement,
|
||||||
|
handleChange: (filename?: string, content?: string) => void,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Make input element draggable
|
||||||
|
* @param t i18n function
|
||||||
|
* @param toast Toast function
|
||||||
|
* @param target Input element
|
||||||
|
* @param handleChange Handle change function
|
||||||
|
* @example
|
||||||
|
* const input = document.getElementById("input") as HTMLLabelElement;
|
||||||
|
* useDraggableInput(t, toast, input, handleChange);
|
||||||
|
*/
|
||||||
|
|
||||||
|
target.addEventListener("dragover", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
target.addEventListener("drop", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const file = e.dataTransfer?.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const data = e.target?.result as string;
|
||||||
|
if (!/^[\x00-\x7F]*$/.test(data)) {
|
||||||
|
toast({
|
||||||
|
title: t("file.parse-error"),
|
||||||
|
description: t("file.parse-error-prompt"),
|
||||||
|
});
|
||||||
|
handleChange();
|
||||||
|
} else {
|
||||||
|
handleChange(file.name, e.target?.result as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
handleChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testNumberInputEvent(e: any): boolean {
|
||||||
|
/**
|
||||||
|
* Test if input event is valid for number input
|
||||||
|
* @param e Input event
|
||||||
|
* @example
|
||||||
|
* const handler = (e: any) => {
|
||||||
|
* if (testNumberInputEvent(e)) {
|
||||||
|
* // do something
|
||||||
|
* }
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (
|
||||||
|
/^[0-9]+$/.test(e.key) ||
|
||||||
|
["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(e.key)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceInputValue(
|
||||||
|
input: HTMLInputElement | undefined,
|
||||||
|
value: string,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Replace input value and focus
|
||||||
|
* @param input Input element
|
||||||
|
* @param value New value
|
||||||
|
* @example
|
||||||
|
* const input = document.getElementById("input") as HTMLInputElement;
|
||||||
|
* replaceInputValue(input, "Hello world!");
|
||||||
|
*/
|
||||||
|
|
||||||
|
return input && (input.value = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInputValue(id: string, value: string) {
|
||||||
|
/**
|
||||||
|
* Replace input value and focus
|
||||||
|
* @param id Input element id
|
||||||
|
* @param value New value
|
||||||
|
* @example
|
||||||
|
* const input = document.getElementById("input") as HTMLInputElement;
|
||||||
|
* useInputValue("input", "Hello world!");
|
||||||
|
*/
|
||||||
|
|
||||||
|
const input = document.getElementById(id) as HTMLInputElement | undefined;
|
||||||
|
return input && replaceInputValue(input, value) && input.focus();
|
||||||
|
}
|
79
app/src/utils/hook.ts
Normal file
79
app/src/utils/hook.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
export function useEffectAsync<T>(effect: () => Promise<T>, deps?: any[]) {
|
||||||
|
/**
|
||||||
|
* useEffect with async/await support
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* useEffectAsync(async () => {
|
||||||
|
* const result = await fetch("https://api.example.com");
|
||||||
|
* console.log(result);
|
||||||
|
* }, []);
|
||||||
|
*/
|
||||||
|
|
||||||
|
return useEffect(() => {
|
||||||
|
effect().catch((err) =>
|
||||||
|
console.debug("[runtime] error during use effect", err),
|
||||||
|
);
|
||||||
|
}, deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAnimation(
|
||||||
|
ref: React.MutableRefObject<any>,
|
||||||
|
cls: string,
|
||||||
|
min?: number,
|
||||||
|
): (() => number) | undefined {
|
||||||
|
/**
|
||||||
|
* Add animation class to react ref element and remove it after min ms when returned function is called
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const animation = useAnimation(ref, "animate", 1000);
|
||||||
|
* axios.get("https://api.example.com")
|
||||||
|
* .finally(() => animation());
|
||||||
|
*/
|
||||||
|
if (!ref.current) return;
|
||||||
|
const target = ref.current as HTMLButtonElement;
|
||||||
|
const stamp = Date.now();
|
||||||
|
target.classList.add(cls);
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
const duration = Date.now() - stamp;
|
||||||
|
const timeout = min ? Math.max(min - duration, 0) : 0;
|
||||||
|
setTimeout(() => target.classList.remove(cls), timeout);
|
||||||
|
return timeout;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useShared<T>(): {
|
||||||
|
hook: (v: T) => void;
|
||||||
|
useHook: () => Promise<T>;
|
||||||
|
} {
|
||||||
|
/**
|
||||||
|
* Share value between components, useful for sharing data between components / redux dispatches
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const dispatch = useDispatch();
|
||||||
|
* const { hook, useHook } = useShared<string>();
|
||||||
|
*
|
||||||
|
* dispatch(updateMigration({ hook }));
|
||||||
|
* const response = await useHook();
|
||||||
|
*/
|
||||||
|
let value: T | undefined = undefined;
|
||||||
|
return {
|
||||||
|
hook: (v: T) => {
|
||||||
|
value = v;
|
||||||
|
},
|
||||||
|
useHook: () => {
|
||||||
|
return new Promise<T>((resolve) => {
|
||||||
|
if (value) return resolve(value);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (value) {
|
||||||
|
clearInterval(interval);
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
31
app/src/utils/path.ts
Normal file
31
app/src/utils/path.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export function getQueryParams() {
|
||||||
|
/**
|
||||||
|
* Get query params from url
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // https://example.com?foo=bar&baz=qux
|
||||||
|
* getQueryParams();
|
||||||
|
* // { foo: "bar", baz: "qux" }
|
||||||
|
*/
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const obj: Record<string, string> = {};
|
||||||
|
for (const [key, value] of params.entries()) {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQueryParam(key: string): string {
|
||||||
|
/**
|
||||||
|
* Get query param from url
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // https://example.com?foo=bar&baz=qux
|
||||||
|
* getQueryParam("foo");
|
||||||
|
* // "bar"
|
||||||
|
*/
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
return params.get(key) || "";
|
||||||
|
}
|
63
app/src/utils/processor.ts
Normal file
63
app/src/utils/processor.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { FileObject } from "@/components/FileProvider.tsx";
|
||||||
|
|
||||||
|
export function formatMessage(file: FileObject, message: string): string {
|
||||||
|
message = message.trim();
|
||||||
|
if (file.name.length > 0 || file.content.length > 0) {
|
||||||
|
return `
|
||||||
|
\`\`\`file
|
||||||
|
[[${file.name}]]
|
||||||
|
${file.content}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
${message}`;
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterMessage(message: string): string {
|
||||||
|
return message.replace(/```file\n\[\[.*]]\n[\s\S]*?\n```\n\n/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractMessage(
|
||||||
|
message: string,
|
||||||
|
length: number = 50,
|
||||||
|
flow: string = "...",
|
||||||
|
) {
|
||||||
|
return message.length > length ? message.slice(0, length) + flow : message;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function escapeRegExp(str: string): string {
|
||||||
|
// convert \n to [enter], \t to [tab], \r to [return], \s to [space], \" to [quote], \' to [single-quote]
|
||||||
|
return str
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\r/g, "\r")
|
||||||
|
.replace(/\\s/g, " ")
|
||||||
|
.replace(/\\"/g, '"')
|
||||||
|
.replace(/\\'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleLine(
|
||||||
|
data: string,
|
||||||
|
max_line: number,
|
||||||
|
end?: boolean,
|
||||||
|
): string {
|
||||||
|
const segment = data.split("\n");
|
||||||
|
const line = segment.length;
|
||||||
|
if (line > max_line) {
|
||||||
|
return end ?? true
|
||||||
|
? segment.slice(line - max_line).join("\n")
|
||||||
|
: segment.slice(0, max_line).join("\n");
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleGenerationData(data: string): string {
|
||||||
|
data = data
|
||||||
|
.replace(/{\s*"result":\s*{/g, "")
|
||||||
|
.trim()
|
||||||
|
.replace(/}\s*$/g, "");
|
||||||
|
return handleLine(escapeRegExp(data), 6);
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": [
|
"types": [
|
||||||
"vite-plugin-pwa/react",
|
"vite-plugin-pwa/react"
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
@ -21,12 +21,15 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": [
|
||||||
|
"src",
|
||||||
|
"*.ts",
|
||||||
|
],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }],
|
"references": [{ "path": "./tsconfig.node.json" }],
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user