feat: maskpage&newchatpage adapt new ui framework done

This commit is contained in:
butterfly 2024-04-19 11:55:51 +08:00
parent b3559f99a2
commit 3d0a98d5d2
40 changed files with 2273 additions and 774 deletions

View File

@ -50,7 +50,7 @@ export default function IconButton(props: {
autoFocus={autoFocus}
>
{text && (
<div className={`text-common text-sm-title leading-4 line-clamp-1`}>
<div className={`font-common text-sm-title leading-4 line-clamp-1`}>
{text}
</div>
)}

View File

@ -0,0 +1,18 @@
import BotIcon from "@/app/icons/bot.svg";
import LoadingIcon from "@/app/icons/three-dots.svg";
export default function GloablLoading({
noLogo,
}: {
noLogo?: boolean;
useSkeleton?: boolean;
}) {
return (
<div
className={`flex flex-col justify-center items-center w-[100%] h-[100%]`}
>
{!noLogo && <BotIcon />}
<LoadingIcon />
</div>
);
}

View File

@ -0,0 +1,85 @@
import { useNavigate } from "react-router-dom";
import { Path } from "@/app/constant";
import useDragSideBar from "@/app/hooks/useDragSideBar";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import {
ComponentType,
Context,
createContext,
useContext,
useState,
} from "react";
import DragIcon from "@/app/icons/drag.svg";
export interface MenuWrapperInspectProps {
setShowPanel?: (v: boolean) => void;
showPanel?: boolean;
}
export default function MenuWrapper<
ListComponentProps extends MenuWrapperInspectProps,
PanelComponentProps extends MenuWrapperInspectProps,
>(
ListComponent: ComponentType<ListComponentProps>,
PanelComponent: ComponentType<PanelComponentProps>,
) {
return function MenuHood(props: ListComponentProps & PanelComponentProps) {
const [showPanel, setShowPanel] = useState(false);
const navigate = useNavigate();
const isMobileScreen = useMobileScreen();
// drag side bar
const { onDragStart } = useDragSideBar();
let containerClassName = "flex h-[100%] w-[100%]";
let listClassName =
"relative basis-sidebar h-[calc(100%-1.25rem)] pb-6 max-md:px-4 max-md:pb-4 rounded-md my-2.5 bg-gray-50";
let panelClassName = "flex-1 h-[100%] w-page";
if (isMobileScreen) {
containerClassName = "h-[100%] w-[100%] relative bg-center";
listClassName = `h-[100%] w-[100%] flex-1 px-4`;
panelClassName = `transition-all duration-300 absolute top-0 max-h-[100vh] w-[100%] ${
showPanel ? "left-0" : "left-[101%]"
} z-10`;
}
return (
<div className={`${containerClassName}`}>
<div
className={`flex flex-col px-6 ${listClassName}`}
onClick={(e) => {
if (e.target === e.currentTarget) {
navigate(Path.Home);
}
}}
>
<ListComponent
{...props}
setShowPanel={setShowPanel}
showPanel={showPanel}
/>
{!isMobileScreen && (
<div
className={`group absolute right-0 h-[100%] flex items-center`}
onPointerDown={(e) => onDragStart(e as any)}
>
<div className="opacity-0 group-hover:bg-[rgba($color: #000000, $alpha: 0.01) group-hover:opacity-20">
<DragIcon />
</div>
</div>
)}
</div>
<div className={`${panelClassName}`}>
<PanelComponent
{...props}
setShowPanel={setShowPanel}
showPanel={showPanel}
/>
</div>
</div>
);
};
}

View File

@ -18,7 +18,6 @@ interface ScreenProps {
export default function Screen(props: ScreenProps) {
const location = useLocation();
const isAuth = location.pathname === Path.Auth;
const isHome = location.pathname === Path.Home;
const isMobileScreen = useMobileScreen();
const isIOSMobile = useMemo(
@ -28,16 +27,15 @@ export default function Screen(props: ScreenProps) {
useListenWinResize();
let containerClassName = "flex h-[100%] w-[100%]";
let pageClassName = "flex-1 h-[100%] w-page";
let sidebarClassName = "basis-sidebar h-[100%]";
let containerClassName = "flex h-[100%] w-[100%] bg-center overflow-hidden";
let sidebarClassName = "flex-0 overflow-hidden";
let pageClassName = "flex-1 h-[100%] min-w-0 overflow-hidden";
if (isMobileScreen) {
containerClassName = "h-[100%] w-[100%] relative bg-center";
pageClassName = `absolute top-0 h-[100%] w-[100%] ${
!isHome ? "left-0" : "left-[101%]"
} z-10`;
sidebarClassName = `h-[100%] w-[100%]`;
containerClassName =
"relative flex flex-col-reverse h-[100%] w-[100%] bg-center";
sidebarClassName = "absolute w-[100%] bottom-0 z-10";
pageClassName = "w-[100%] h-[100%]";
}
return (

View File

@ -1,5 +1,4 @@
import { IconButton } from "./button";
import { ErrorBoundary } from "./error";
import styles from "./mask.module.scss";
@ -56,6 +55,7 @@ import {
OnDragEndResponder,
} from "@hello-pangea/dnd";
import { getMessageTextContent } from "../utils";
import useMobileScreen from "@/app/hooks/useMobileScreen";
// drag and drop helper function
function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
@ -465,9 +465,15 @@ export function MaskPage() {
});
};
const isMobileScreen = useMobileScreen();
return (
<ErrorBoundary>
<div className={styles["mask-page"]}>
<>
<div
className={`${styles["mask-page"]} !bg-gray-50 ${
isMobileScreen ? "pb-chat-panel-mobile" : ""
}`}
>
<div className="window-header">
<div className="window-header-title">
<div className="window-header-main-title">
@ -645,6 +651,6 @@ export function MaskPage() {
</Modal>
</div>
)}
</ErrorBoundary>
</>
);
}

View File

@ -16,6 +16,7 @@ import { MaskAvatar } from "./mask";
import { useCommand } from "../command";
import { showConfirm } from "./ui-lib";
import { BUILTIN_MASK_STORE } from "../masks";
import useMobileScreen from "@/app/hooks/useMobileScreen";
function MaskItem(props: { mask: Mask; onClick?: () => void }) {
return (
@ -110,8 +111,14 @@ export function NewChat() {
}
}, [groups]);
const isMobileScreen = useMobileScreen();
return (
<div className={styles["new-chat"]}>
<div
className={`${styles["new-chat"]} !bg-gray-50 px-1 ${
isMobileScreen ? "pb-chat-panel-mobile" : ""
}`}
>
<div className={styles["mask-header"]}>
<IconButton
icon={<LeftIcon />}

View File

@ -49,9 +49,9 @@ export enum StoreKey {
Sync = "sync",
}
export const DEFAULT_SIDEBAR_WIDTH = 404;
export const MAX_SIDEBAR_WIDTH = 504;
export const MIN_SIDEBAR_WIDTH = 294;
export const DEFAULT_SIDEBAR_WIDTH = 340;
export const MAX_SIDEBAR_WIDTH = 440;
export const MIN_SIDEBAR_WIDTH = 230;
export const WINDOW_WIDTH_SM = 480;
export const WINDOW_WIDTH_MD = 768;

View File

@ -1,52 +0,0 @@
import { useRef, useState } from "react";
import styles from "./index.module.scss";
export default function ChatAction(props: {
text: string;
icon: JSX.Element;
onClick: () => void;
}) {
const iconRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState({
full: 16,
icon: 16,
});
function updateWidth() {
if (!iconRef.current || !textRef.current) return;
const getWidth = (dom: HTMLDivElement) => dom.getBoundingClientRect().width;
const textWidth = getWidth(textRef.current);
const iconWidth = getWidth(iconRef.current);
setWidth({
full: textWidth + iconWidth,
icon: iconWidth,
});
}
return (
<div
className={`${styles["chat-input-action"]} clickable`}
onClick={() => {
props.onClick();
setTimeout(updateWidth, 1);
}}
onMouseEnter={updateWidth}
onTouchStart={updateWidth}
style={
{
"--icon-width": `${width.icon}px`,
"--full-width": `${width.full}px`,
} as React.CSSProperties
}
>
<div ref={iconRef} className={styles["icon"]}>
{props.icon}
</div>
<div className={styles["text"]} ref={textRef}>
{props.text}
</div>
</div>
);
}

View File

@ -176,14 +176,17 @@ export function ChatActions(props: {
if (props.isMobileScreen) {
const content = (
<div className="w-[100%]">
{actions.map((act) => {
{actions
.filter((v) => v.isShow)
.map((act) => {
return (
<div
key={act.text}
className={`flex items-center gap-3 p-3 bg-white hover:bg-select-btn rounded-action-btn leading-6`}
onClick={act.onClick}
>
{act.icon}
<div className="flex-1 text-common text-actions-popover-menu-item">
<div className="flex-1 font-common text-actions-popover-menu-item">
{act.text}
</div>
</div>

View File

@ -1,4 +1,4 @@
import { forwardRef, useImperativeHandle, useMemo, useState } from "react";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useDebouncedCallback } from "use-debounce";
import useUploadImage from "@/app/hooks/useUploadImage";
@ -10,7 +10,6 @@ import { ChatCommandPrefix, useChatCommand } from "@/app/command";
import { useChatStore } from "@/app/store/chat";
import { usePromptStore } from "@/app/store/prompt";
import { useAppConfig } from "@/app/store/config";
import useScrollToBottom from "@/app/hooks/useScrollToBottom";
import usePaste from "@/app/hooks/usePaste";
import { ChatActions } from "./ChatActions";
@ -168,10 +167,14 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
useImperativeHandle(ref, () => ({
setUploading,
doSubmit,
setAutoScroll,
setMsgRenderIndex,
}));
function scrollToBottom() {
setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE);
scrollDomToBottom();
}
const onInput = (text: string) => {
setUserInput(text);
const n = text.trim().length;
@ -196,11 +199,6 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
_setMsgRenderIndex(newIndex);
}
function scrollToBottom() {
setMsgRenderIndex(renderMessages.length - CHAT_PAGE_SIZE);
scrollDomToBottom();
}
const { handlePaste } = usePaste(attachImages, {
emitImages: setAttachImages,
setUploading,

View File

@ -0,0 +1,334 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
useChatStore,
BOT_HELLO,
createMessage,
useAccessStore,
useAppConfig,
ModelType,
} from "@/app/store";
import Locale from "@/app/locales";
import { Selector, showConfirm, showToast } from "@/app/components/ui-lib";
import {
CHAT_PAGE_SIZE,
REQUEST_TIMEOUT_MS,
UNFINISHED_INPUT,
} from "@/app/constant";
import { useCommand } from "@/app/command";
import { prettyObject } from "@/app/utils/format";
import { ExportMessageModal } from "@/app/components/exporter";
import PromptToast from "./PromptToast";
import { EditMessageModal } from "./EditMessageModal";
import ChatHeader from "./ChatHeader";
import ChatInputPanel, { ChatInputPanelInstance } from "./ChatInputPanel";
import ChatMessagePanel, { RenderMessage } from "./ChatMessagePanel";
import { useAllModels } from "@/app/utils/hooks";
import useRows from "@/app/hooks/useRows";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import SessionConfigModel from "./SessionConfigModal";
import useScrollToBottom from "@/app/hooks/useScrollToBottom";
function _Chat() {
const chatStore = useChatStore();
const session = chatStore.currentSession();
const config = useAppConfig();
const [showExport, setShowExport] = useState(false);
const [showModelSelector, setShowModelSelector] = useState(false);
const inputRef = useRef<HTMLTextAreaElement>(null);
const [userInput, setUserInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
const chatInputPanelRef = useRef<ChatInputPanelInstance | null>(null);
const [hitBottom, setHitBottom] = useState(true);
const isMobileScreen = useMobileScreen();
const [attachImages, setAttachImages] = useState<string[]>([]);
// auto grow input
const { measure, inputRows } = useRows({
inputRef,
});
const { setAutoScroll, scrollDomToBottom } = useScrollToBottom(scrollRef);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(measure, [userInput]);
useEffect(() => {
chatStore.updateCurrentSession((session) => {
const stopTiming = Date.now() - REQUEST_TIMEOUT_MS;
session.messages.forEach((m) => {
// check if should stop all stale messages
if (m.isError || new Date(m.date).getTime() < stopTiming) {
if (m.streaming) {
m.streaming = false;
}
if (m.content.length === 0) {
m.isError = true;
m.content = prettyObject({
error: true,
message: "empty response",
});
}
}
});
// auto sync mask config from global config
if (session.mask.syncGlobalConfig) {
console.log("[Mask] syncing from global, name = ", session.mask.name);
session.mask.modelConfig = { ...config.modelConfig };
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const context: RenderMessage[] = useMemo(() => {
return session.mask.hideContext ? [] : session.mask.context.slice();
}, [session.mask.context, session.mask.hideContext]);
const accessStore = useAccessStore();
if (
context.length === 0 &&
session.messages.at(0)?.content !== BOT_HELLO.content
) {
const copiedHello = Object.assign({}, BOT_HELLO);
if (!accessStore.isAuthorized()) {
copiedHello.content = Locale.Error.Unauthorized;
}
context.push(copiedHello);
}
// preview messages
const renderMessages = useMemo(() => {
return context
.concat(session.messages as RenderMessage[])
.concat(
isLoading
? [
{
...createMessage({
role: "assistant",
content: "……",
}),
preview: true,
},
]
: [],
)
.concat(
userInput.length > 0 && config.sendPreviewBubble
? [
{
...createMessage(
{
role: "user",
content: userInput,
},
{
customId: "typing",
},
),
preview: true,
},
]
: [],
);
}, [
config.sendPreviewBubble,
context,
isLoading,
session.messages,
userInput,
]);
const [msgRenderIndex, _setMsgRenderIndex] = useState(
Math.max(0, renderMessages.length - CHAT_PAGE_SIZE),
);
const [showPromptModal, setShowPromptModal] = useState(false);
useCommand({
fill: setUserInput,
submit: (text) => {
chatInputPanelRef.current?.doSubmit(text);
},
code: (text) => {
if (accessStore.disableFastLink) return;
console.log("[Command] got code from url: ", text);
showConfirm(Locale.URLCommand.Code + `code = ${text}`).then((res) => {
if (res) {
accessStore.update((access) => (access.accessCode = text));
}
});
},
settings: (text) => {
if (accessStore.disableFastLink) return;
try {
const payload = JSON.parse(text) as {
key?: string;
url?: string;
};
console.log("[Command] got settings from url: ", payload);
if (payload.key || payload.url) {
showConfirm(
Locale.URLCommand.Settings +
`\n${JSON.stringify(payload, null, 4)}`,
).then((res) => {
if (!res) return;
if (payload.key) {
accessStore.update(
(access) => (access.openaiApiKey = payload.key!),
);
}
if (payload.url) {
accessStore.update((access) => (access.openaiUrl = payload.url!));
}
});
}
} catch {
console.error("[Command] failed to get settings from url: ", text);
}
},
});
// edit / insert message modal
const [isEditingMessage, setIsEditingMessage] = useState(false);
// remember unfinished input
useEffect(() => {
// try to load from local storage
const key = UNFINISHED_INPUT(session.id);
const mayBeUnfinishedInput = localStorage.getItem(key);
if (mayBeUnfinishedInput && userInput.length === 0) {
setUserInput(mayBeUnfinishedInput);
localStorage.removeItem(key);
}
const dom = inputRef.current;
return () => {
localStorage.setItem(key, dom?.value ?? "");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const chatinputPanelProps = {
inputRef,
isMobileScreen,
renderMessages,
attachImages,
userInput,
hitBottom,
inputRows,
setAttachImages,
setUserInput,
setIsLoading,
showChatSetting: setShowPromptModal,
_setMsgRenderIndex,
showModelSelector: setShowModelSelector,
scrollDomToBottom,
setAutoScroll,
};
const chatMessagePanelProps = {
scrollRef,
inputRef,
isMobileScreen,
msgRenderIndex,
userInput,
context,
renderMessages,
setAutoScroll,
setMsgRenderIndex: chatInputPanelRef.current?.setMsgRenderIndex,
setHitBottom,
setUserInput,
setIsLoading,
setShowPromptModal,
scrollDomToBottom,
};
const currentModel = chatStore.currentSession().mask.modelConfig.model;
const allModels = useAllModels();
const models = useMemo(
() => allModels.filter((m) => m.available),
[allModels],
);
return (
<div
className={`flex flex-col ${
isMobileScreen
? "absolute h-[100vh] w-[100%]"
: "h-[calc(100%-1.25rem)]"
} overflow-hidden ${
isMobileScreen ? "" : `my-2.5 ml-1 mr-2.5 rounded-md`
} bg-chat-panel`}
key={session.id}
>
<ChatHeader
setIsEditingMessage={setIsEditingMessage}
setShowExport={setShowExport}
isMobileScreen={isMobileScreen}
showModelSelector={setShowModelSelector}
/>
<ChatMessagePanel {...chatMessagePanelProps} />
<ChatInputPanel ref={chatInputPanelRef} {...chatinputPanelProps} />
{showExport && (
<ExportMessageModal onClose={() => setShowExport(false)} />
)}
{isEditingMessage && (
<EditMessageModal
onClose={() => {
setIsEditingMessage(false);
}}
/>
)}
<PromptToast
showToast={!hitBottom}
showModal={showPromptModal}
setShowModal={setShowPromptModal}
/>
{showPromptModal && (
<SessionConfigModel onClose={() => setShowPromptModal(false)} />
)}
{showModelSelector && (
<Selector
defaultSelectedValue={currentModel}
items={models.map((m) => ({
title: m.displayName,
value: m.name,
}))}
onClose={() => setShowModelSelector(false)}
onSelection={(s) => {
if (s.length === 0) return;
chatStore.updateCurrentSession((session) => {
session.mask.modelConfig.model = s[0] as ModelType;
session.mask.syncGlobalConfig = false;
});
showToast(s[0]);
}}
/>
)}
</div>
);
}
export default function Chat() {
const chatStore = useChatStore();
const sessionIndex = chatStore.currentSessionIndex;
return <_Chat key={sessionIndex}></_Chat>;
}

View File

@ -49,7 +49,7 @@ const genActionsShema = (
(message: RenderMessage) => void
>,
) => {
const className = "!p-1 hover:bg-gray-100 !rounded-actions-bar-btn";
const className = " !p-1 hover:bg-gray-100 !rounded-actions-bar-btn ";
return [
{
id: "Edit",
@ -231,9 +231,19 @@ export default function MessageActions(props: MessageActionsProps) {
return (
showActions && (
<div
className={`transition-all duration-500 absolute z-10 ${
isUser ? "right-0" : "left-0"
} opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-all bg-white rounded-md shadow-actions-bar ${className}`}
className={`
absolute z-10
${isUser ? "right-0" : "left-0"}
transition-all duration-500
opacity-0
pointer-events-none
group-hover:opacity-100
group-hover:pointer-events-auto
bg-white
rounded-md
shadow-actions-bar
${className}
`}
>
<ActionsBar
actionsShema={genActionsShema(message, {

View File

@ -68,7 +68,7 @@ export default function PromptHints(props: {
<div
className={`
${styles["prompt-hints"]}
transition-all duration-300 shadow-inner rounded-none w-[100%] flex flex-col-reverse overflow-auto
transition-all duration-300 shadow-prompt-hint-container rounded-none w-[100%] flex flex-col-reverse overflow-auto
${
notShowPrompt
? "max-h-[0vh] border-none"

View File

@ -1,327 +1,230 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
useChatStore,
BOT_HELLO,
createMessage,
useAccessStore,
useAppConfig,
ModelType,
} from "@/app/store";
DragDropContext,
Droppable,
Draggable,
OnDragEndResponder,
} from "@hello-pangea/dnd";
import { useAppConfig, useChatStore } from "@/app/store";
import Locale from "@/app/locales";
import { Selector, showConfirm, showToast } from "@/app/components/ui-lib";
import {
CHAT_PAGE_SIZE,
REQUEST_TIMEOUT_MS,
UNFINISHED_INPUT,
} from "@/app/constant";
import { useCommand } from "@/app/command";
import { prettyObject } from "@/app/utils/format";
import { ExportMessageModal } from "@/app/components/exporter";
import { useLocation, useNavigate } from "react-router-dom";
import { Path } from "@/app/constant";
import { Mask } from "@/app/store/mask";
import { useRef, useEffect, useMemo, useContext } from "react";
import { showConfirm } from "@/app/components/ui-lib";
import PromptToast from "./PromptToast";
import { EditMessageModal } from "./EditMessageModal";
import ChatHeader from "./ChatHeader";
import ChatInputPanel, { ChatInputPanelInstance } from "./ChatInputPanel";
import ChatMessagePanel, { RenderMessage } from "./ChatMessagePanel";
import { useAllModels } from "@/app/utils/hooks";
import useRows from "@/app/hooks/useRows";
import AddIcon from "@/app/icons/addIcon.svg";
import NextChatTitle from "@/app/icons/nextchatTitle.svg";
// import { ListHoodProps } from "@/app/containers/types";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import SessionConfigModel from "./SessionConfigModal";
import useScrollToBottom from "@/app/hooks/useScrollToBottom";
import { getTime } from "@/app/utils";
import DeleteIcon from "@/app/icons/deleteIcon.svg";
import LogIcon from "@/app/icons/logIcon.svg";
function _Chat() {
const chatStore = useChatStore();
const session = chatStore.currentSession();
const config = useAppConfig();
const [showExport, setShowExport] = useState(false);
const [showModelSelector, setShowModelSelector] = useState(false);
const inputRef = useRef<HTMLTextAreaElement>(null);
const [userInput, setUserInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
const chatInputPanelRef = useRef<ChatInputPanelInstance | null>(null);
const [hitBottom, setHitBottom] = useState(true);
const isMobileScreen = useMobileScreen();
const [attachImages, setAttachImages] = useState<string[]>([]);
// auto grow input
const { measure, inputRows } = useRows({
inputRef,
});
const { setAutoScroll, scrollDomToBottom } = useScrollToBottom(scrollRef);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(measure, [userInput]);
import MenuWrapper, {
MenuWrapperInspectProps,
} from "@/app/components/MenuWrapper";
import Panel from "./ChatPanel";
export function SessionItem(props: {
onClick?: () => void;
onDelete?: () => void;
title: string;
count: number;
time: string;
selected: boolean;
id: string;
index: number;
narrow?: boolean;
mask: Mask;
}) {
const draggableRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
chatStore.updateCurrentSession((session) => {
const stopTiming = Date.now() - REQUEST_TIMEOUT_MS;
session.messages.forEach((m) => {
// check if should stop all stale messages
if (m.isError || new Date(m.date).getTime() < stopTiming) {
if (m.streaming) {
m.streaming = false;
}
if (m.content.length === 0) {
m.isError = true;
m.content = prettyObject({
error: true,
message: "empty response",
if (props.selected && draggableRef.current) {
draggableRef.current?.scrollIntoView({
block: "center",
});
}
}
});
}, [props.selected]);
// auto sync mask config from global config
if (session.mask.syncGlobalConfig) {
console.log("[Mask] syncing from global, name = ", session.mask.name);
session.mask.modelConfig = { ...config.modelConfig };
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const context: RenderMessage[] = useMemo(() => {
return session.mask.hideContext ? [] : session.mask.context.slice();
}, [session.mask.context, session.mask.hideContext]);
const accessStore = useAccessStore();
if (
context.length === 0 &&
session.messages.at(0)?.content !== BOT_HELLO.content
) {
const copiedHello = Object.assign({}, BOT_HELLO);
if (!accessStore.isAuthorized()) {
copiedHello.content = Locale.Error.Unauthorized;
}
context.push(copiedHello);
}
// preview messages
const renderMessages = useMemo(() => {
return context
.concat(session.messages as RenderMessage[])
.concat(
isLoading
? [
{
...createMessage({
role: "assistant",
content: "……",
}),
preview: true,
},
]
: [],
)
.concat(
userInput.length > 0 && config.sendPreviewBubble
? [
{
...createMessage({
role: "user",
content: userInput,
}),
preview: true,
},
]
: [],
);
}, [
config.sendPreviewBubble,
context,
isLoading,
session.messages,
userInput,
]);
const [msgRenderIndex, _setMsgRenderIndex] = useState(
Math.max(0, renderMessages.length - CHAT_PAGE_SIZE),
);
const [showPromptModal, setShowPromptModal] = useState(false);
useCommand({
fill: setUserInput,
submit: (text) => {
chatInputPanelRef.current?.doSubmit(text);
},
code: (text) => {
if (accessStore.disableFastLink) return;
console.log("[Command] got code from url: ", text);
showConfirm(Locale.URLCommand.Code + `code = ${text}`).then((res) => {
if (res) {
accessStore.update((access) => (access.accessCode = text));
}
});
},
settings: (text) => {
if (accessStore.disableFastLink) return;
try {
const payload = JSON.parse(text) as {
key?: string;
url?: string;
};
console.log("[Command] got settings from url: ", payload);
if (payload.key || payload.url) {
showConfirm(
Locale.URLCommand.Settings +
`\n${JSON.stringify(payload, null, 4)}`,
).then((res) => {
if (!res) return;
if (payload.key) {
accessStore.update(
(access) => (access.openaiApiKey = payload.key!),
);
}
if (payload.url) {
accessStore.update((access) => (access.openaiUrl = payload.url!));
}
});
}
} catch {
console.error("[Command] failed to get settings from url: ", text);
}
},
});
// edit / insert message modal
const [isEditingMessage, setIsEditingMessage] = useState(false);
// remember unfinished input
useEffect(() => {
// try to load from local storage
const key = UNFINISHED_INPUT(session.id);
const mayBeUnfinishedInput = localStorage.getItem(key);
if (mayBeUnfinishedInput && userInput.length === 0) {
setUserInput(mayBeUnfinishedInput);
localStorage.removeItem(key);
}
const dom = inputRef.current;
return () => {
localStorage.setItem(key, dom?.value ?? "");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const chatinputPanelProps = {
inputRef,
isMobileScreen,
renderMessages,
attachImages,
userInput,
hitBottom,
inputRows,
setAttachImages,
setUserInput,
setIsLoading,
showChatSetting: setShowPromptModal,
_setMsgRenderIndex,
showModelSelector: setShowModelSelector,
scrollDomToBottom,
setAutoScroll,
};
const chatMessagePanelProps = {
scrollRef,
inputRef,
isMobileScreen,
msgRenderIndex,
userInput,
context,
renderMessages,
setAutoScroll,
setMsgRenderIndex: chatInputPanelRef.current?.setMsgRenderIndex,
setHitBottom,
setUserInput,
setIsLoading,
setShowPromptModal,
scrollDomToBottom,
};
const currentModel = chatStore.currentSession().mask.modelConfig.model;
const allModels = useAllModels();
const models = useMemo(
() => allModels.filter((m) => m.available),
[allModels],
);
const { pathname: currentPath } = useLocation();
return (
<Draggable draggableId={`${props.id}`} index={props.index}>
{(provided) => (
<div
className={`flex flex-col ${
isMobileScreen ? "h-[100%]" : "h-[calc(100%-1.25rem)]"
} overflow-hidden ${
isMobileScreen ? "" : `my-2.5 ml-1 mr-2.5 rounded-md`
} bg-chat-panel`}
key={session.id}
className={`group relative flex p-3 items-center gap-2 self-stretch rounded-md mb-2 ${
props.selected &&
(currentPath === Path.Chat || currentPath === Path.Home)
? `bg-blue-100 border-blue-200 border `
: `bg-gray-100 hover:bg-gray-200`
}`}
onClick={props.onClick}
ref={(ele) => {
draggableRef.current = ele;
provided.innerRef(ele);
}}
{...provided.draggableProps}
{...provided.dragHandleProps}
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
props.count,
)}`}
>
<ChatHeader
setIsEditingMessage={setIsEditingMessage}
setShowExport={setShowExport}
isMobileScreen={isMobileScreen}
showModelSelector={setShowModelSelector}
/>
<ChatMessagePanel {...chatMessagePanelProps} />
<ChatInputPanel ref={chatInputPanelRef} {...chatinputPanelProps} />
{showExport && (
<ExportMessageModal onClose={() => setShowExport(false)} />
)}
{isEditingMessage && (
<EditMessageModal
onClose={() => {
setIsEditingMessage(false);
}}
/>
)}
<PromptToast
showToast={!hitBottom}
showModal={showPromptModal}
setShowModal={setShowPromptModal}
/>
{showPromptModal && (
<SessionConfigModel onClose={() => setShowPromptModal(false)} />
)}
{showModelSelector && (
<Selector
defaultSelectedValue={currentModel}
items={models.map((m) => ({
title: m.displayName,
value: m.name,
}))}
onClose={() => setShowModelSelector(false)}
onSelection={(s) => {
if (s.length === 0) return;
chatStore.updateCurrentSession((session) => {
session.mask.modelConfig.model = s[0] as ModelType;
session.mask.syncGlobalConfig = false;
});
showToast(s[0]);
}}
/>
)}
<div className=" flex-shrink-0">
<LogIcon />
</div>
<div className="flex flex-col flex-1">
<div className={`flex justify-between items-center`}>
<div
className={` text-gray-900 text-sm-title line-clamp-1 flex-1`}
>
{props.title}
</div>
<div
className={`text-gray-500 text-sm group-hover:opacity-0 pl-3`}
>
{getTime(props.time)}
</div>
</div>
<div className={`text-gray-500 text-sm`}>
{Locale.ChatItem.ChatItemCount(props.count)}
</div>
</div>
<div
className={`absolute top-[50%] translate-y-[-50%] right-3 pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100`}
onClickCapture={(e) => {
props.onDelete?.();
e.preventDefault();
e.stopPropagation();
}}
>
<DeleteIcon />
</div>
</div>
)}
</Draggable>
);
}
export default function Chat() {
export default MenuWrapper(function SessionList(props) {
const { setShowPanel } = props;
const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
(state) => [
state.sessions,
state.currentSessionIndex,
state.selectSession,
state.moveSession,
],
);
const navigate = useNavigate();
const isMobileScreen = useMobileScreen();
const config = useAppConfig();
const chatStore = useChatStore();
const sessionIndex = chatStore.currentSessionIndex;
return <_Chat key={sessionIndex}></_Chat>;
}
const { pathname: currentPath } = useLocation();
useEffect(() => {
setShowPanel?.(currentPath === Path.Chat);
}, [currentPath]);
const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
moveSession(source.index, destination.index);
};
let layoutClassName = "flex flex-col py-7 px-0";
if (isMobileScreen) {
layoutClassName = "flex flex-col py-6 pb-chat-panel-mobile ";
}
return (
<div className={`h-[100%] ${layoutClassName}`}>
<div data-tauri-drag-region>
<div
className={`flex items-center justify-between`}
data-tauri-drag-region
>
<div className="">
<NextChatTitle />
</div>
<div
className=""
onClick={() => {
if (config.dontShowMaskSplashScreen) {
chatStore.newSession();
navigate(Path.Chat);
} else {
navigate(Path.NewChat);
}
}}
>
<AddIcon />
</div>
</div>
<div className={`pb-3 text-sm sm:text-sm-mobile text-blue-500`}>
Build your own AI assistant.
</div>
</div>
<div
className={`flex-1 overflow-y-auto overflow-x-hidden`}
onClick={(e) => {
if (e.target === e.currentTarget) {
navigate(Path.Home);
}
}}
>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="chat-list">
{(provided) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className={`w-[100%]`}
>
{sessions.map((item, i) => (
<SessionItem
title={item.topic}
time={new Date(item.lastUpdate).toLocaleString()}
count={item.messages.length}
key={item.id}
id={item.id}
index={i}
selected={i === selectedIndex}
onClick={() => {
navigate(Path.Chat);
selectSession(i);
}}
onDelete={async () => {
if (
!isMobileScreen ||
(await showConfirm(Locale.Home.DeleteChat))
) {
chatStore.deleteSession(i);
}
}}
mask={item.mask}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</div>
);
}, Panel);

View File

@ -0,0 +1,51 @@
import { useNavigate } from "react-router-dom";
import { Path } from "@/app/constant";
import Locale from "@/app/locales";
import GobackIcon from "@/app/icons/goback.svg";
export interface ChatHeaderProps {
isMobileScreen: boolean;
goback: () => void;
}
export default function SettingHeader(props: ChatHeaderProps) {
const { isMobileScreen, goback } = props;
const navigate = useNavigate();
let containerClassName = "";
let titleClassName = "mr-4";
let mainTitleClassName = "";
let subTitleClassName = "";
if (isMobileScreen) {
containerClassName = "h-menu-title-mobile";
titleClassName = "flex flex-col items-center justify-center gap-0.5 text";
mainTitleClassName = "text-sm-title h-[19px] leading-5";
subTitleClassName = "text-sm-mobile-tab leading-4";
}
return (
<div
className={`relative flex flex-0 justify-between items-center px-6 py-4 gap-chat-header-gap border-b-[1px] border-gray-200 ${containerClassName}`}
data-tauri-drag-region
>
{isMobileScreen ? (
<div
className="absolute left-4 top-[50%] translate-y-[-50%]"
onClick={() => goback()}
>
<GobackIcon />
</div>
) : null}
<div className={`flex-1 ${titleClassName}`}>
<div
className={`line-clamp-1 cursor-pointer text-black text-chat-header-title font-common ${mainTitleClassName}`}
>
{Locale.Settings.Title}
</div>
</div>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
.settings {
padding: 20px;
overflow: auto;
}
.avatar {
cursor: pointer;
position: relative;
z-index: 1;
}
.edit-prompt-modal {
display: flex;
flex-direction: column;
.edit-prompt-title {
max-width: unset;
margin-bottom: 20px;
text-align: left;
}
.edit-prompt-content {
max-width: unset;
}
}
.user-prompt-modal {
min-height: 40vh;
.user-prompt-search {
width: 100%;
max-width: 100%;
margin-bottom: 10px;
background-color: var(--gray);
}
.user-prompt-list {
border: var(--border-in-light);
border-radius: 10px;
.user-prompt-item {
display: flex;
justify-content: space-between;
padding: 10px;
&:not(:last-child) {
border-bottom: var(--border-in-light);
}
.user-prompt-header {
max-width: calc(100% - 100px);
.user-prompt-title {
font-size: 14px;
line-height: 2;
font-weight: bold;
}
.user-prompt-content {
font-size: 12px;
}
}
.user-prompt-buttons {
display: flex;
align-items: center;
column-gap: 2px;
.user-prompt-button {
//height: 100%;
padding: 7px;
}
}
}
}
}

View File

@ -0,0 +1,61 @@
import Locale from "@/app/locales";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import MenuWrapper from "@/app/components/MenuWrapper";
import Panel from "./SettingPanel";
import GotoIcon from "@/app/icons/goto.svg";
export default MenuWrapper(function SettingList(props) {
const { setShowPanel } = props;
const isMobileScreen = useMobileScreen();
let layoutClassName = "pt-7 px-4";
let titleClassName = "pb-5";
let itemClassName = "";
if (isMobileScreen) {
layoutClassName = "h-[100%] mx-[-1.5rem] px-6 py-6 bg-blue-50";
titleClassName = "h-menu-title-mobile";
itemClassName = "p-4 bg-white";
}
return (
<div className={` ${layoutClassName}`}>
<div data-tauri-drag-region>
<div
className={`flex items-center justify-between ${titleClassName}`}
data-tauri-drag-region
>
<div className="text-setting-title text-black font-common font-setting-title">
{Locale.Settings.Title}
</div>
</div>
{/* <div className={`pb-3 text-sm sm:text-sm-mobile text-blue-500`}>
{Locale.Settings.SubTitle}
</div> */}
</div>
<div
className={`flex flex-col overflow-y-auto overflow-x-hidden w-[100%]`}
>
<div
// className={`p-4 font-common text-setting-items font-normal text-black
// border-[1px] border-blue-200 border-opacity-0 rounded-md
// `}
className={`p-4 font-common text-setting-items font-normal text-black
border-[1px] border-blue-200 border-opacity-0 rounded-md
hover:border-opacity-100 hover:bg-blue-100 ${itemClassName}
flex justify-between items-center
`}
onClick={() => {
setShowPanel?.(true);
}}
>
{Locale.Settings.GeneralSettings}
{isMobileScreen && <GotoIcon />}
</div>
</div>
</div>
);
}, Panel);

View File

@ -1,35 +0,0 @@
import { Path } from "@/app/constant";
import { ComponentType } from "react";
import { useNavigate } from "react-router-dom";
export interface MenuWrapperProps {
show: boolean;
wrapperClassName?: string;
}
export default function MenuWrapper<ComponentProps>(
Component: ComponentType<ComponentProps>,
) {
return function MenuHood(props: MenuWrapperProps & ComponentProps) {
const { show, wrapperClassName } = props;
const navigate = useNavigate();
if (!show) {
return null;
}
return (
<div
className={`flex flex-col px-6 pb-6 ${wrapperClassName}`}
onClick={(e) => {
if (e.target === e.currentTarget) {
navigate(Path.Home);
}
}}
>
<Component {...props} />
</div>
);
};
}

View File

@ -1,220 +0,0 @@
import {
DragDropContext,
Droppable,
Draggable,
OnDragEndResponder,
} from "@hello-pangea/dnd";
import { useAppConfig, useChatStore } from "@/app/store";
import Locale from "@/app/locales";
import { useLocation, useNavigate } from "react-router-dom";
import { Path } from "@/app/constant";
import { Mask } from "@/app/store/mask";
import { useRef, useEffect, useMemo } from "react";
import { showConfirm } from "@/app/components/ui-lib";
import AddIcon from "@/app/icons/addIcon.svg";
import NextChatTitle from "@/app/icons/nextchatTitle.svg";
import { ListHoodProps } from "./types";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import { getTime } from "@/app/utils";
import DeleteIcon from "@/app/icons/deleteIcon.svg";
import LogIcon from "@/app/icons/logIcon.svg";
export function SessionItem(props: {
onClick?: () => void;
onDelete?: () => void;
title: string;
count: number;
time: string;
selected: boolean;
id: string;
index: number;
narrow?: boolean;
mask: Mask;
}) {
const draggableRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (props.selected && draggableRef.current) {
draggableRef.current?.scrollIntoView({
block: "center",
});
}
}, [props.selected]);
const { pathname: currentPath } = useLocation();
return (
<Draggable draggableId={`${props.id}`} index={props.index}>
{(provided) => (
<div
className={`group relative flex p-3 items-center gap-2 self-stretch rounded-md hover:bg-gray-200 mb-2 ${
props.selected &&
(currentPath === Path.Chat || currentPath === Path.Home)
? `bg-blue-100 border-blue-200 border`
: `bg-gray-100`
}`}
onClick={props.onClick}
ref={(ele) => {
draggableRef.current = ele;
provided.innerRef(ele);
}}
{...provided.draggableProps}
{...provided.dragHandleProps}
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
props.count,
)}`}
>
<div className=" flex-shrink-0">
<LogIcon />
</div>
<div className="flex flex-col flex-1">
<div className={`flex justify-between items-center`}>
<div
className={` text-gray-900 text-sm-title line-clamp-1 flex-1`}
>
{props.title}
</div>
<div
className={`text-gray-500 text-sm group-hover:opacity-0 pl-3`}
>
{getTime(props.time)}
</div>
</div>
<div className={`text-gray-500 text-sm`}>
{Locale.ChatItem.ChatItemCount(props.count)}
</div>
</div>
<div
className={`absolute top-[50%] translate-y-[-50%] right-3 pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100`}
onClickCapture={(e) => {
props.onDelete?.();
e.preventDefault();
e.stopPropagation();
}}
>
<DeleteIcon />
</div>
</div>
)}
</Draggable>
);
}
export default function SessionList(props: ListHoodProps) {
const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
(state) => [
state.sessions,
state.currentSessionIndex,
state.selectSession,
state.moveSession,
],
);
const chatStore = useChatStore();
const navigate = useNavigate();
const isMobileScreen = useMobileScreen();
const config = useAppConfig();
const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
moveSession(source.index, destination.index);
};
let layoutClassName = "py-7 px-0";
if (isMobileScreen) {
layoutClassName = "h-menu-title-mobile py-6";
}
return (
<>
<div data-tauri-drag-region>
<div
className={`flex items-center justify-between ${layoutClassName}`}
data-tauri-drag-region
>
<div className="">
<NextChatTitle />
</div>
<div
className=""
onClick={() => {
if (config.dontShowMaskSplashScreen) {
chatStore.newSession();
navigate(Path.Chat);
} else {
navigate(Path.NewChat);
}
}}
>
<AddIcon />
</div>
</div>
<div className={`pb-3 text-sm sm:text-sm-mobile text-blue-500`}>
Build your own AI assistant.
</div>
</div>
<div
className={`flex overflow-y-auto overflow-x-hidden`}
onClick={(e) => {
if (e.target === e.currentTarget) {
navigate(Path.Home);
}
}}
>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="chat-list">
{(provided) => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
className="w-[100%]"
>
{sessions.map((item, i) => (
<SessionItem
title={item.topic}
time={new Date(item.lastUpdate).toLocaleString()}
count={item.messages.length}
key={item.id}
id={item.id}
index={i}
selected={i === selectedIndex}
onClick={() => {
navigate(Path.Chat);
selectSession(i);
}}
onDelete={async () => {
if (
!isMobileScreen ||
(await showConfirm(Locale.Home.DeleteChat))
) {
chatStore.deleteSession(i);
}
}}
mask={item.mask}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</>
);
}

View File

@ -1,5 +0,0 @@
import { ListHoodProps } from "./types";
export default function SettingList(props: ListHoodProps) {
return <></>;
}

View File

@ -1,42 +1,25 @@
import DragIcon from "@/app/icons/drag.svg";
import DiscoverIcon from "@/app/icons/discoverActive.svg";
import AssistantActiveIcon from "@/app/icons/assistantActive.svg";
import GitHubIcon from "@/app/icons/githubIcon.svg";
import SettingIcon from "@/app/icons/settingActive.svg";
import DiscoverIcon from "@/app/icons/discoverActive.svg";
import DiscoverInactiveIcon from "@/app/icons/discoverInactive.svg";
import AssistantInactiveIcon from "@/app/icons/assistantInactive.svg";
import DiscoverMobileActive from "@/app/icons/discoverMobileActive.svg";
import DiscoverMobileInactive from "@/app/icons/discoverMobileInactive.svg";
import SettingIcon from "@/app/icons/settingActive.svg";
import SettingInactiveIcon from "@/app/icons/settingInactive.svg";
import SettingMobileActive from "@/app/icons/settingMobileActive.svg";
import DiscoverMobileActive from "@/app/icons/discoverMobileActive.svg";
import SettingMobileInactive from "@/app/icons/settingMobileInactive.svg";
import AssistantActiveIcon from "@/app/icons/assistantActive.svg";
import AssistantInactiveIcon from "@/app/icons/assistantInactive.svg";
import AssistantMobileActive from "@/app/icons/assistantMobileActive.svg";
import AssistantMobileInactive from "@/app/icons/assistantMobileInactive.svg";
import { useAppConfig } from "@/app/store";
import { Path, REPO_URL } from "@/app/constant";
import { useNavigate, useLocation } from "react-router-dom";
import dynamic from "next/dynamic";
import useHotKey from "@/app/hooks/useHotKey";
import useDragSideBar from "@/app/hooks/useDragSideBar";
import useMobileScreen from "@/app/hooks/useMobileScreen";
import MenuWrapper from "./MenuWrapper";
import ActionsBar from "@/app/components/ActionsBar";
const SessionList = MenuWrapper(
dynamic(async () => await import("./SessionList"), {
loading: () => null,
}),
);
const SettingList = MenuWrapper(
dynamic(async () => await import("./SettingList"), {
loading: () => null,
}),
);
export function SideBar(props: { className?: string }) {
// drag side bar
const { onDragStart } = useDragSideBar();
const navigate = useNavigate();
const loc = useLocation();
@ -56,18 +39,15 @@ export function SideBar(props: { className?: string }) {
selectedTab = Path.Settings;
break;
default:
selectedTab = Path.Chat;
selectedTab = Path.Home;
}
let containerClassName = "relative flex h-[100%] w-[100%]";
let containerClassName = "relative flex h-[100%]";
let tabActionsClassName = "2xl:px-5 xl:px-4 px-2 py-6";
let menuClassName =
"max-md:px-4 max-md:pb-4 rounded-md my-2.5 bg-gray-50 flex-1";
if (isMobileScreen) {
containerClassName = "flex flex-col-reverse w-[100%] h-[100%]";
tabActionsClassName = "bg-gray-100 rounded-tl-md rounded-tr-md h-mobile";
menuClassName = `flex-1 px-4`;
}
return (
@ -81,12 +61,12 @@ export function SideBar(props: { className?: string }) {
active: <DiscoverIcon />,
inactive: <DiscoverInactiveIcon />,
mobileActive: <DiscoverMobileActive />,
mobileInactive: <DiscoverInactiveIcon />,
mobileInactive: <DiscoverMobileInactive />,
},
title: "Discover",
},
{
id: Path.Chat,
id: Path.Home,
icons: {
active: <AssistantActiveIcon />,
inactive: <AssistantInactiveIcon />,
@ -106,7 +86,7 @@ export function SideBar(props: { className?: string }) {
active: <SettingIcon />,
inactive: <SettingInactiveIcon />,
mobileActive: <SettingMobileActive />,
mobileInactive: <SettingInactiveIcon />,
mobileInactive: <SettingMobileInactive />,
},
className: "p-2",
title: "Settrings",
@ -127,36 +107,16 @@ export function SideBar(props: { className?: string }) {
}}
groups={{
normal: [
[Path.Chat, Path.Masks],
[Path.Home, Path.Masks],
["github", Path.Settings],
],
mobile: [[Path.Chat, Path.Masks, Path.Settings]],
mobile: [[Path.Home, Path.Masks, Path.Settings]],
}}
selected={selectedTab}
className={`${
isMobileScreen ? "justify-around" : "flex-col"
} ${tabActionsClassName}`}
/>
<SessionList
show={selectedTab === Path.Chat}
wrapperClassName={menuClassName}
/>
<SettingList
show={selectedTab === Path.Settings}
wrapperClassName={menuClassName}
/>
{!isMobileScreen && (
<div
className={`group absolute right-0 h-[100%] flex items-center`}
onPointerDown={(e) => onDragStart(e as any)}
>
<div className="opacity-0 group-hover:bg-[rgba($color: #000000, $alpha: 0.01) group-hover:opacity-20">
<DragIcon />
</div>
</div>
)}
</div>
);
}

View File

@ -1,4 +0,0 @@
export interface ListHoodProps {
// narrow?: boolean;
className?: string;
}

View File

@ -17,9 +17,10 @@ import { useLoadData } from "@/app/hooks/useLoadData";
import Loading from "@/app/components/Loading";
import Screen from "@/app/components/Screen";
import { SideBar } from "./Sidebar";
import GlobalLoading from "@/app/components/GlobalLoading";
const Settings = dynamic(
async () => (await import("@/app/components/settings")).Settings,
async () => await import("@/app/containers/Settings"),
{
loading: () => <Loading noLogo />,
},
@ -94,7 +95,7 @@ export default function Home() {
}, []);
if (!useHasHydrated()) {
return <Loading />;
return <GlobalLoading />;
}
return (

View File

@ -10,7 +10,6 @@ import {
MIN_SIDEBAR_WIDTH,
} from "@/app/constant";
import { useAppConfig } from "../store/config";
import { useReducer, useState } from "react";
export const MOBILE_MAX_WIDTH = 768;
@ -25,8 +24,6 @@ const widths = [
export default function useListenWinResize() {
const config = useAppConfig();
const [_, refresh] = useReducer((x) => x + 1, 0);
useWindowSize((size) => {
let nextSidebar = config.sidebarWidth;
if (!nextSidebar) {
@ -56,6 +53,5 @@ export default function useListenWinResize() {
config.update((config) => {
config.sidebarWidth = nextSidebar;
});
refresh();
});
}

View File

@ -1,16 +1,9 @@
import { useWindowSize } from "@/app/hooks/useWindowSize";
import { useRef } from "react";
export const MOBILE_MAX_WIDTH = 768;
export default function useMobileScreen() {
const widthRef = useRef<number>(0);
const { width } = useWindowSize();
useWindowSize((size) => {
widthRef.current = size.width;
});
const isMobile = widthRef.current <= MOBILE_MAX_WIDTH;
return isMobile;
return width <= MOBILE_MAX_WIDTH;
}

View File

@ -10,9 +10,13 @@ export default function useScrollToBottom(
) <= 1
: false;
const initScrolled = useRef(false);
// for auto-scroll
const [autoScroll, setAutoScroll] = useState(true);
const autoScrollRef = useRef<typeof autoScroll>();
autoScrollRef.current = autoScroll;
function scrollDomToBottom() {
const dom = scrollRef.current;
if (dom) {
@ -23,13 +27,30 @@ export default function useScrollToBottom(
}
}
// useEffect(() => {
// const dom = scrollRef.current;
// if (dom) {
// dom.ontouchstart = (e) => {
// const autoScroll = autoScrollRef.current;
// if (autoScroll) {
// setAutoScroll(false);
// }
// }
// dom.onscroll = (e) => {
// const autoScroll = autoScrollRef.current;
// if (autoScroll) {
// setAutoScroll(false);
// }
// }
// }
// }, []);
// auto scroll
useEffect(() => {
if (autoScroll && !detach && !initScrolled.current) {
if (autoScroll && !detach) {
scrollDomToBottom();
initScrolled.current = true;
}
}, [autoScroll, detach]);
});
return {
scrollRef,

View File

@ -1,26 +1,35 @@
import { useLayoutEffect, useRef } from "react";
import { useLayoutEffect, useRef, useState } from "react";
type Size = {
width: number;
height: number;
};
export function useWindowSize(callback: (size: Size) => void) {
export function useWindowSize(callback?: (size: Size) => void) {
const callbackRef = useRef<typeof callback>();
callbackRef.current = callback;
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useLayoutEffect(() => {
const onResize = () => {
callbackRef.current?.({
width: window.innerWidth,
height: window.innerHeight,
});
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", onResize);
callback({
callback?.({
width: window.innerWidth,
height: window.innerHeight,
});
@ -29,4 +38,6 @@ export function useWindowSize(callback: (size: Size) => void) {
window.removeEventListener("resize", onResize);
};
}, []);
return size;
}

View File

@ -1,3 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4C2 2.89543 2.89543 2 4 2H12H12.1639V2.00132C17.6112 2.08887 22 6.5319 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 11.947 2.00041 11.8941 2.00123 11.8413H2V4ZM7.57373 9.78713C7.57373 9.10809 8.1242 8.55762 8.80324 8.55762C9.48228 8.55762 10.0327 9.10809 10.0327 9.78713V11.2625C10.0327 11.9416 9.48228 12.492 8.80324 12.492C8.1242 12.492 7.57373 11.9416 7.57373 11.2625V9.78713ZM13.9673 9.78713C13.9673 9.10809 14.5178 8.55762 15.1968 8.55762C15.8758 8.55762 16.4263 9.10809 16.4263 9.78713V11.2625C16.4263 11.9416 15.8758 12.492 15.1968 12.492C14.5178 12.492 13.9673 11.9416 13.9673 11.2625V9.78713Z" fill="#A5A5B3"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_769_8890" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 2C3.39543 2 2.5 2.89543 2.5 4V11.8413H2.50123C2.50041 11.8941 2.5 11.947 2.5 12C2.5 17.5228 6.97715 22 12.5 22C18.0228 22 22.5 17.5228 22.5 12C22.5 6.5319 18.1112 2.08887 12.6639 2.00132V2H12.5H4.5Z"/>
</mask>
<path d="M2.5 11.8413H0.7V13.6413H2.5V11.8413ZM2.50123 11.8413L4.30102 11.8693L4.32946 10.0413H2.50123V11.8413ZM12.6639 2.00132H10.8639V3.77262L12.635 3.80108L12.6639 2.00132ZM12.6639 2H14.4639V0.2H12.6639V2ZM4.3 4C4.3 3.88954 4.38954 3.8 4.5 3.8V0.2C2.40132 0.2 0.7 1.90131 0.7 4H4.3ZM4.3 11.8413V4H0.7V11.8413H4.3ZM2.50123 10.0413H2.5V13.6413H2.50123V10.0413ZM4.3 12C4.3 11.9563 4.30034 11.9128 4.30102 11.8693L0.701452 11.8133C0.700485 11.8754 0.7 11.9377 0.7 12H4.3ZM12.5 20.2C7.97126 20.2 4.3 16.5287 4.3 12H0.7C0.7 18.517 5.98304 23.8 12.5 23.8V20.2ZM20.7 12C20.7 16.5287 17.0287 20.2 12.5 20.2V23.8C19.017 23.8 24.3 18.517 24.3 12H20.7ZM12.635 3.80108C17.1011 3.87287 20.7 7.51632 20.7 12H24.3C24.3 5.54748 19.1212 0.30487 12.6929 0.201549L12.635 3.80108ZM10.8639 2V2.00132H14.4639V2H10.8639ZM12.5 3.8H12.6639V0.2H12.5V3.8ZM4.5 3.8H12.5V0.2H4.5V3.8Z" fill="#A5A5B3" mask="url(#path-1-inside-1_769_8890)"/>
<path opacity="0.89" fill-rule="evenodd" clip-rule="evenodd" d="M9.30324 8.55762C8.6242 8.55762 8.07373 9.10809 8.07373 9.78713V11.2625C8.07373 11.9416 8.6242 12.492 9.30324 12.492C9.98228 12.492 10.5327 11.9416 10.5327 11.2625V9.78713C10.5327 9.10809 9.98228 8.55762 9.30324 8.55762ZM15.6968 8.55762C15.0178 8.55762 14.4673 9.10809 14.4673 9.78713V11.2625C14.4673 11.9416 15.0178 12.492 15.6968 12.492C16.3758 12.492 16.9263 11.9416 16.9263 11.2625V9.78713C16.9263 9.10809 16.3758 8.55762 15.6968 8.55762Z" fill="#A5A5B3"/>
</svg>

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,9 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C6.47727 22 2 17.5227 2 12C2 6.47727 6.47727 2 12 2C17.5227 2 22 6.47727 22 12C22 17.5227 17.5227 22 12 22ZM10.5036 10.0409C10.3013 10.1327 10.1397 10.2953 10.0491 10.4982L7.70364 15.7555C7.66577 15.8399 7.65455 15.9338 7.67148 16.0247C7.68841 16.1157 7.73269 16.1993 7.79839 16.2644C7.8641 16.3295 7.94811 16.373 8.0392 16.3891C8.13029 16.4052 8.22413 16.3932 8.30818 16.3545L13.5291 13.9664C13.7322 13.8735 13.894 13.7091 13.9836 13.5045L16.2655 8.29455C16.3024 8.21026 16.313 8.11673 16.2956 8.02634C16.2783 7.93594 16.234 7.85293 16.1684 7.78829C16.1029 7.72365 16.0193 7.68043 15.9287 7.66434C15.8381 7.64825 15.7447 7.66005 15.6609 7.69818L10.5036 10.0409Z" fill="url(#paint0_linear_496_51074)"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 22C6.97727 22 2.5 17.5227 2.5 12C2.5 6.47727 6.97727 2 12.5 2C18.0227 2 22.5 6.47727 22.5 12C22.5 17.5227 18.0227 22 12.5 22ZM11.0036 10.0409C10.8013 10.1327 10.6397 10.2953 10.5491 10.4982L8.20364 15.7555C8.16577 15.8399 8.15455 15.9338 8.17148 16.0247C8.18841 16.1157 8.23269 16.1993 8.29839 16.2644C8.3641 16.3295 8.44811 16.373 8.5392 16.3891C8.63029 16.4052 8.72413 16.3932 8.80818 16.3545L14.0291 13.9664C14.2322 13.8735 14.394 13.7091 14.4836 13.5045L16.7655 8.29455C16.8024 8.21026 16.813 8.11673 16.7956 8.02634C16.7783 7.93594 16.734 7.85293 16.6684 7.78829C16.6029 7.72365 16.5193 7.68043 16.4287 7.66434C16.3381 7.64825 16.2447 7.66005 16.1609 7.69818L11.0036 10.0409Z" fill="url(#paint0_linear_769_8893)"/>
<defs>
<linearGradient id="paint0_linear_496_51074" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
<stop stop-color="#E5E6FF"/>
<stop offset="1" stop-color="white"/>
<linearGradient id="paint0_linear_769_8893" x1="2.5" y1="12" x2="22.522" y2="12" gradientUnits="userSpaceOnUse">
<stop stop-color="#2A33FF"/>
<stop offset="0.997219" stop-color="#7963FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,7 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_460_34354" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0001 21.1001C6.97444 21.1001 2.9001 17.0258 2.9001 12.0001C2.9001 6.97444 6.97444 2.9001 12.0001 2.9001C17.0258 2.9001 21.1001 6.97444 21.1001 12.0001C21.1001 17.0258 17.0258 21.1001 12.0001 21.1001ZM12.0001 22.9001C5.98032 22.9001 1.1001 18.0199 1.1001 12.0001C1.1001 5.98032 5.98032 1.1001 12.0001 1.1001C18.0199 1.1001 22.9001 5.98032 22.9001 12.0001C22.9001 18.0199 18.0199 22.9001 12.0001 22.9001ZM10.6296 10.1822C10.4423 10.2662 10.2926 10.4151 10.2087 10.6008L8.03701 15.4136C8.00194 15.4909 7.99155 15.5769 8.00723 15.6602C8.02291 15.7434 8.06391 15.82 8.12475 15.8796C8.18559 15.9392 8.26337 15.979 8.34772 15.9938C8.43206 16.0085 8.51895 15.9975 8.59677 15.9621L13.431 13.7758C13.619 13.6908 13.7688 13.5403 13.8518 13.353L15.9646 8.58346C15.9989 8.5063 16.0086 8.42068 15.9926 8.33793C15.9766 8.25517 15.9355 8.17918 15.8748 8.12C15.8141 8.06083 15.7367 8.02126 15.6528 8.00653C15.5689 7.9918 15.4824 8.0026 15.4049 8.03751L10.6296 10.1822Z"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_769_8003" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5001 21.1001C7.47444 21.1001 3.4001 17.0258 3.4001 12.0001C3.4001 6.97444 7.47444 2.9001 12.5001 2.9001C17.5258 2.9001 21.6001 6.97444 21.6001 12.0001C21.6001 17.0258 17.5258 21.1001 12.5001 21.1001ZM12.5001 22.9001C6.48032 22.9001 1.6001 18.0199 1.6001 12.0001C1.6001 5.98032 6.48032 1.1001 12.5001 1.1001C18.5199 1.1001 23.4001 5.98032 23.4001 12.0001C23.4001 18.0199 18.5199 22.9001 12.5001 22.9001ZM11.1296 10.1822C10.9423 10.2662 10.7926 10.4151 10.7087 10.6008L8.53701 15.4136C8.50194 15.4909 8.49155 15.5769 8.50723 15.6602C8.52291 15.7434 8.56391 15.82 8.62475 15.8796C8.68559 15.9392 8.76337 15.979 8.84772 15.9938C8.93206 16.0085 9.01895 15.9975 9.09677 15.9621L13.931 13.7758C14.119 13.6908 14.2688 13.5403 14.3518 13.353L16.4646 8.58346C16.4989 8.5063 16.5086 8.42068 16.4926 8.33793C16.4766 8.25517 16.4355 8.17918 16.3748 8.12C16.3141 8.06083 16.2367 8.02126 16.1528 8.00653C16.0689 7.9918 15.9824 8.0026 15.9049 8.03751L11.1296 10.1822Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0001 21.1001C6.97444 21.1001 2.9001 17.0258 2.9001 12.0001C2.9001 6.97444 6.97444 2.9001 12.0001 2.9001C17.0258 2.9001 21.1001 6.97444 21.1001 12.0001C21.1001 17.0258 17.0258 21.1001 12.0001 21.1001ZM12.0001 22.9001C5.98032 22.9001 1.1001 18.0199 1.1001 12.0001C1.1001 5.98032 5.98032 1.1001 12.0001 1.1001C18.0199 1.1001 22.9001 5.98032 22.9001 12.0001C22.9001 18.0199 18.0199 22.9001 12.0001 22.9001ZM10.6296 10.1822C10.4423 10.2662 10.2926 10.4151 10.2087 10.6008L8.03701 15.4136C8.00194 15.4909 7.99155 15.5769 8.00723 15.6602C8.02291 15.7434 8.06391 15.82 8.12475 15.8796C8.18559 15.9392 8.26337 15.979 8.34772 15.9938C8.43206 16.0085 8.51895 15.9975 8.59677 15.9621L13.431 13.7758C13.619 13.6908 13.7688 13.5403 13.8518 13.353L15.9646 8.58346C15.9989 8.5063 16.0086 8.42068 15.9926 8.33793C15.9766 8.25517 15.9355 8.17918 15.8748 8.12C15.8141 8.06083 15.7367 8.02126 15.6528 8.00653C15.5689 7.9918 15.4824 8.0026 15.4049 8.03751L10.6296 10.1822Z" fill="#A5A5B3"/>
<path d="M10.2087 10.6008L8.56823 9.86003L8.56803 9.86047L10.2087 10.6008ZM10.6296 10.1822L11.3662 11.8246L11.3671 11.8242L10.6296 10.1822ZM8.03701 15.4136L9.67611 16.1575L9.67771 16.154L8.03701 15.4136ZM8.00723 15.6602L9.77614 15.3271L9.77614 15.3271L8.00723 15.6602ZM8.12475 15.8796L6.86502 17.1653L6.86503 17.1653L8.12475 15.8796ZM8.34772 15.9938L8.6577 14.2207L8.65767 14.2207L8.34772 15.9938ZM8.59677 15.9621L7.85504 14.322L7.85205 14.3234L8.59677 15.9621ZM13.431 13.7758L12.6894 12.1357L12.6892 12.1357L13.431 13.7758ZM13.8518 13.353L15.4974 14.0825L15.4976 14.0821L13.8518 13.353ZM15.9646 8.58346L14.3194 7.85319L14.3189 7.85443L15.9646 8.58346ZM15.4049 8.03751L16.1423 9.67951L16.1436 9.67893L15.4049 8.03751ZM1.1001 12.0001C1.1001 18.0199 5.98032 22.9001 12.0001 22.9001V19.3001C7.96855 19.3001 4.7001 16.0316 4.7001 12.0001H1.1001ZM12.0001 1.1001C5.98032 1.1001 1.1001 5.98032 1.1001 12.0001H4.7001C4.7001 7.96855 7.96855 4.7001 12.0001 4.7001V1.1001ZM22.9001 12.0001C22.9001 5.98032 18.0199 1.1001 12.0001 1.1001V4.7001C16.0316 4.7001 19.3001 7.96855 19.3001 12.0001H22.9001ZM12.0001 22.9001C18.0199 22.9001 22.9001 18.0199 22.9001 12.0001H19.3001C19.3001 16.0316 16.0316 19.3001 12.0001 19.3001V22.9001ZM-0.699902 12.0001C-0.699902 19.014 4.98621 24.7001 12.0001 24.7001V21.1001C6.97444 21.1001 2.9001 17.0258 2.9001 12.0001H-0.699902ZM12.0001 -0.699902C4.98621 -0.699902 -0.699902 4.98621 -0.699902 12.0001H2.9001C2.9001 6.97444 6.97444 2.9001 12.0001 2.9001V-0.699902ZM24.7001 12.0001C24.7001 4.98621 19.014 -0.699902 12.0001 -0.699902V2.9001C17.0258 2.9001 21.1001 6.97444 21.1001 12.0001H24.7001ZM12.0001 24.7001C19.014 24.7001 24.7001 19.014 24.7001 12.0001H21.1001C21.1001 17.0258 17.0258 21.1001 12.0001 21.1001V24.7001ZM11.8492 11.3416C11.7506 11.5601 11.5769 11.7301 11.3662 11.8246L9.89297 8.53983C9.30769 8.80234 8.83461 9.27013 8.56823 9.86003L11.8492 11.3416ZM9.67771 16.154L11.8494 11.3412L8.56803 9.86047L6.39631 14.6733L9.67771 16.154ZM9.77614 15.3271C9.82909 15.6082 9.79377 15.8983 9.6761 16.1575L6.39791 14.6698C6.21011 15.0836 6.15402 15.5456 6.23832 15.9933L9.77614 15.3271ZM9.38447 14.5938C9.58536 14.7907 9.72322 15.046 9.77614 15.3271L6.23832 15.9933C6.3226 16.4408 6.54246 16.8493 6.86502 17.1653L9.38447 14.5938ZM8.65767 14.2207C8.92992 14.2682 9.18384 14.3973 9.38446 14.5938L6.86503 17.1653C7.18733 17.4811 7.59683 17.6898 8.03777 17.7669L8.65767 14.2207ZM7.85205 14.3234C8.10481 14.2085 8.38558 14.1731 8.6577 14.2207L8.03773 17.7669C8.47854 17.8439 8.93308 17.7864 9.3415 17.6008L7.85205 14.3234ZM12.6892 12.1357L7.85504 14.322L9.33851 17.6022L14.1727 15.4159L12.6892 12.1357ZM12.2063 12.6236C12.304 12.4031 12.4778 12.2313 12.6894 12.1357L14.1726 15.4159C14.7602 15.1502 15.2337 14.6774 15.4974 14.0825L12.2063 12.6236ZM14.3189 7.85443L12.2061 12.624L15.4976 14.0821L17.6104 9.31249L14.3189 7.85443ZM14.2255 8.68043C14.1713 8.401 14.2045 8.11222 14.3194 7.85319L17.6098 9.31374C17.7933 8.90039 17.846 8.44037 17.7597 7.99543L14.2255 8.68043ZM14.618 9.4086C14.4177 9.21323 14.2796 8.95973 14.2255 8.68043L17.7597 7.99543C17.6735 7.55061 17.4533 7.14512 17.1316 6.83141L14.618 9.4086ZM15.3416 9.77942C15.0708 9.73188 14.8181 9.60373 14.618 9.4086L17.1316 6.83141C16.8102 6.51793 16.4027 6.31063 15.964 6.23364L15.3416 9.77942ZM16.1436 9.67893C15.8917 9.79231 15.6123 9.82694 15.3416 9.77942L15.964 6.23364C15.5255 6.15666 15.0732 6.2129 14.6661 6.39609L16.1436 9.67893ZM11.3671 11.8242L16.1423 9.67951L14.6674 6.39552L9.89215 8.5402L11.3671 11.8242Z" fill="#A5A5B3" mask="url(#path-1-inside-1_460_34354)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5001 21.1001C7.47444 21.1001 3.4001 17.0258 3.4001 12.0001C3.4001 6.97444 7.47444 2.9001 12.5001 2.9001C17.5258 2.9001 21.6001 6.97444 21.6001 12.0001C21.6001 17.0258 17.5258 21.1001 12.5001 21.1001ZM12.5001 22.9001C6.48032 22.9001 1.6001 18.0199 1.6001 12.0001C1.6001 5.98032 6.48032 1.1001 12.5001 1.1001C18.5199 1.1001 23.4001 5.98032 23.4001 12.0001C23.4001 18.0199 18.5199 22.9001 12.5001 22.9001ZM11.1296 10.1822C10.9423 10.2662 10.7926 10.4151 10.7087 10.6008L8.53701 15.4136C8.50194 15.4909 8.49155 15.5769 8.50723 15.6602C8.52291 15.7434 8.56391 15.82 8.62475 15.8796C8.68559 15.9392 8.76337 15.979 8.84772 15.9938C8.93206 16.0085 9.01895 15.9975 9.09677 15.9621L13.931 13.7758C14.119 13.6908 14.2688 13.5403 14.3518 13.353L16.4646 8.58346C16.4989 8.5063 16.5086 8.42068 16.4926 8.33793C16.4766 8.25517 16.4355 8.17918 16.3748 8.12C16.3141 8.06083 16.2367 8.02126 16.1528 8.00653C16.0689 7.9918 15.9824 8.0026 15.9049 8.03751L11.1296 10.1822Z" fill="#A5A5B3"/>
<path d="M10.7087 10.6008L9.06823 9.86003L9.06803 9.86047L10.7087 10.6008ZM11.1296 10.1822L11.8662 11.8246L11.8671 11.8242L11.1296 10.1822ZM8.53701 15.4136L10.1761 16.1575L10.1777 16.154L8.53701 15.4136ZM8.50723 15.6602L10.2761 15.3271L10.2761 15.3271L8.50723 15.6602ZM8.62475 15.8796L7.36502 17.1653L7.36503 17.1653L8.62475 15.8796ZM8.84772 15.9938L9.1577 14.2207L9.15767 14.2207L8.84772 15.9938ZM9.09677 15.9621L8.35504 14.322L8.35205 14.3234L9.09677 15.9621ZM13.931 13.7758L13.1894 12.1357L13.1892 12.1357L13.931 13.7758ZM14.3518 13.353L15.9974 14.0825L15.9976 14.0821L14.3518 13.353ZM16.4646 8.58346L14.8194 7.85319L14.8189 7.85443L16.4646 8.58346ZM15.9049 8.03751L16.6423 9.67951L16.6436 9.67893L15.9049 8.03751ZM1.6001 12.0001C1.6001 18.0199 6.48032 22.9001 12.5001 22.9001V19.3001C8.46855 19.3001 5.2001 16.0316 5.2001 12.0001H1.6001ZM12.5001 1.1001C6.48032 1.1001 1.6001 5.98032 1.6001 12.0001H5.2001C5.2001 7.96855 8.46855 4.7001 12.5001 4.7001V1.1001ZM23.4001 12.0001C23.4001 5.98032 18.5199 1.1001 12.5001 1.1001V4.7001C16.5316 4.7001 19.8001 7.96855 19.8001 12.0001H23.4001ZM12.5001 22.9001C18.5199 22.9001 23.4001 18.0199 23.4001 12.0001H19.8001C19.8001 16.0316 16.5316 19.3001 12.5001 19.3001V22.9001ZM-0.199902 12.0001C-0.199902 19.014 5.48621 24.7001 12.5001 24.7001V21.1001C7.47444 21.1001 3.4001 17.0258 3.4001 12.0001H-0.199902ZM12.5001 -0.699902C5.48621 -0.699902 -0.199902 4.98621 -0.199902 12.0001H3.4001C3.4001 6.97444 7.47444 2.9001 12.5001 2.9001V-0.699902ZM25.2001 12.0001C25.2001 4.98621 19.514 -0.699902 12.5001 -0.699902V2.9001C17.5258 2.9001 21.6001 6.97444 21.6001 12.0001H25.2001ZM12.5001 24.7001C19.514 24.7001 25.2001 19.014 25.2001 12.0001H21.6001C21.6001 17.0258 17.5258 21.1001 12.5001 21.1001V24.7001ZM12.3492 11.3416C12.2506 11.5601 12.0769 11.7301 11.8662 11.8246L10.393 8.53983C9.80769 8.80234 9.33461 9.27013 9.06823 9.86003L12.3492 11.3416ZM10.1777 16.154L12.3494 11.3412L9.06803 9.86047L6.89631 14.6733L10.1777 16.154ZM10.2761 15.3271C10.3291 15.6082 10.2938 15.8983 10.1761 16.1575L6.89791 14.6698C6.71011 15.0836 6.65402 15.5456 6.73832 15.9933L10.2761 15.3271ZM9.88447 14.5938C10.0854 14.7907 10.2232 15.046 10.2761 15.3271L6.73832 15.9933C6.8226 16.4408 7.04246 16.8493 7.36502 17.1653L9.88447 14.5938ZM9.15767 14.2207C9.42992 14.2682 9.68384 14.3973 9.88446 14.5938L7.36503 17.1653C7.68733 17.4811 8.09683 17.6898 8.53777 17.7669L9.15767 14.2207ZM8.35205 14.3234C8.60481 14.2085 8.88558 14.1731 9.1577 14.2207L8.53773 17.7669C8.97854 17.8439 9.43308 17.7864 9.8415 17.6008L8.35205 14.3234ZM13.1892 12.1357L8.35504 14.322L9.83851 17.6022L14.6727 15.4159L13.1892 12.1357ZM12.7063 12.6236C12.804 12.4031 12.9778 12.2313 13.1894 12.1357L14.6726 15.4159C15.2602 15.1502 15.7337 14.6774 15.9974 14.0825L12.7063 12.6236ZM14.8189 7.85443L12.7061 12.624L15.9976 14.0821L18.1104 9.31249L14.8189 7.85443ZM14.7255 8.68043C14.6713 8.401 14.7045 8.11222 14.8194 7.85319L18.1098 9.31374C18.2933 8.90039 18.346 8.44037 18.2597 7.99543L14.7255 8.68043ZM15.118 9.4086C14.9177 9.21323 14.7796 8.95973 14.7255 8.68043L18.2597 7.99543C18.1735 7.55061 17.9533 7.14512 17.6316 6.83141L15.118 9.4086ZM15.8416 9.77942C15.5708 9.73188 15.3181 9.60373 15.118 9.4086L17.6316 6.83141C17.3102 6.51793 16.9027 6.31063 16.464 6.23364L15.8416 9.77942ZM16.6436 9.67893C16.3917 9.79231 16.1123 9.82694 15.8416 9.77942L16.464 6.23364C16.0255 6.15666 15.5732 6.2129 15.1661 6.39609L16.6436 9.67893ZM11.8671 11.8242L16.6423 9.67951L15.1674 6.39552L10.3922 8.5402L11.8671 11.8242Z" fill="#A5A5B3" mask="url(#path-1-inside-1_769_8003)"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

3
app/icons/goto.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.57564 2.24239C5.34132 2.4767 5.34132 2.8566 5.57564 3.09091L10.4376 7.95284C10.4636 7.97888 10.4636 8.02109 10.4376 8.04712L5.57564 12.9091C5.34132 13.1434 5.34132 13.5233 5.57564 13.7576C5.80995 13.9919 6.18985 13.9919 6.42417 13.7576L11.2861 8.89565C11.7808 8.40099 11.7808 7.59898 11.2861 7.10432L6.42417 2.24239C6.18985 2.00807 5.80995 2.00807 5.57564 2.24239Z" fill="#A5A5B3"/>
</svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -1,9 +1,9 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.76848 17.7057C7.23852 17.9053 7.74631 17.6097 8.0672 17.2124C8.52265 16.6486 9.21926 16.288 10 16.288C10.7807 16.288 11.4774 16.6486 11.9328 17.2124C12.2537 17.6097 12.7615 17.9053 13.2315 17.7057C14.0061 17.3768 14.7202 16.9332 15.3528 16.3962C15.7186 16.0857 15.7116 15.5429 15.5422 15.094C15.4392 14.8208 15.3828 14.5247 15.3828 14.2154C15.3828 13.0558 16.2086 12.0782 17.2746 11.8048C17.7398 11.6855 18.192 11.383 18.2398 10.9051C18.2672 10.6306 18.2812 10.3521 18.2812 10.0702C18.2812 9.44766 18.2127 8.84109 18.0827 8.2577C17.9926 7.85301 17.6101 7.60594 17.2115 7.49187C16.007 7.1472 15.2251 5.88212 15.4254 4.65919C15.4923 4.25081 15.4335 3.80009 15.1081 3.54445C14.4573 3.03323 13.7282 2.61746 12.9417 2.31819C12.5252 2.1597 12.0809 2.38084 11.768 2.69814C11.3175 3.15489 10.6918 3.43795 10 3.43795C9.30822 3.43795 8.68246 3.15489 8.23202 2.69814C7.91911 2.38084 7.47476 2.1597 7.05826 2.31819C6.27178 2.61746 5.54265 3.03323 4.89191 3.54445C4.5665 3.80009 4.508 4.25085 4.57388 4.65939C4.77111 5.8825 3.97642 7.14178 2.78809 7.48944C2.39017 7.60586 2.00743 7.853 1.91727 8.25769C1.78731 8.84108 1.71875 9.44765 1.71875 10.0702C1.71875 10.3521 1.73279 10.6306 1.76022 10.9051C1.80795 11.383 2.26081 11.6825 2.72602 11.8018C4.12149 12.1598 4.93356 13.7537 4.4533 15.0913C4.29116 15.5428 4.28143 16.0857 4.64721 16.3962C5.27978 16.9332 5.99395 17.3768 6.76848 17.7057ZM13.2031 10.0001C13.2031 11.7691 11.769 13.2032 10 13.2032C8.23096 13.2032 6.79688 11.7691 6.79688 10.0001C6.79688 8.23104 8.23096 6.79695 10 6.79695C11.769 6.79695 13.2031 8.23104 13.2031 10.0001Z" fill="url(#paint0_linear_460_42476)"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.6 9.21994C18.79 9.21994 18.05 7.93994 18.95 6.36994C19.47 5.45994 19.16 4.29994 18.25 3.77994L16.52 2.78994C15.73 2.31994 14.71 2.59994 14.24 3.38994L14.13 3.57994C13.23 5.14994 11.75 5.14994 10.84 3.57994L10.73 3.38994C10.28 2.59994 9.26 2.31994 8.47 2.78994L6.74 3.77994C5.83 4.29994 5.52 5.46994 6.04 6.37994C6.95 7.93994 6.21 9.21994 4.4 9.21994C3.36 9.21994 2.5 10.0699 2.5 11.1199V12.8799C2.5 13.9199 3.35 14.7799 4.4 14.7799C6.21 14.7799 6.95 16.0599 6.04 17.6299C5.52 18.5399 5.83 19.6999 6.74 20.2199L8.47 21.2099C9.26 21.6799 10.28 21.3999 10.75 20.6099L10.86 20.4199C11.76 18.8499 13.24 18.8499 14.15 20.4199L14.26 20.6099C14.73 21.3999 15.75 21.6799 16.54 21.2099L18.27 20.2199C19.18 19.6999 19.49 18.5299 18.97 17.6299C18.06 16.0599 18.8 14.7799 20.61 14.7799C21.65 14.7799 22.51 13.9299 22.51 12.8799V11.1199C22.5 10.0799 21.65 9.21994 20.6 9.21994ZM12.5 15.2499C10.71 15.2499 9.25 13.7899 9.25 11.9999C9.25 10.2099 10.71 8.74994 12.5 8.74994C14.29 8.74994 15.75 10.2099 15.75 11.9999C15.75 13.7899 14.29 15.2499 12.5 15.2499Z" fill="url(#paint0_linear_769_8006)"/>
<defs>
<linearGradient id="paint0_linear_460_42476" x1="10" y1="2.26562" x2="10" y2="17.7697" gradientUnits="userSpaceOnUse">
<stop stop-color="#E5E6FF"/>
<stop offset="1" stop-color="white"/>
<linearGradient id="paint0_linear_769_8006" x1="2.5" y1="11.9999" x2="22.532" y2="11.9999" gradientUnits="userSpaceOnUse">
<stop stop-color="#2A33FF"/>
<stop offset="0.997219" stop-color="#7963FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0001 9.9001C10.8403 9.9001 9.9001 10.8403 9.9001 12.0001C9.9001 13.1599 10.8403 14.1001 12.0001 14.1001C13.1599 14.1001 14.1001 13.1599 14.1001 12.0001C14.1001 10.8403 13.1599 9.9001 12.0001 9.9001ZM8.1001 12.0001C8.1001 9.84619 9.84619 8.1001 12.0001 8.1001C14.154 8.1001 15.9001 9.84619 15.9001 12.0001C15.9001 14.154 14.154 15.9001 12.0001 15.9001C9.84619 15.9001 8.1001 14.154 8.1001 12.0001Z" fill="#A5A5B3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5358 3.84629L14.429 4.03081C13.8781 4.98117 13.0302 5.65737 12.0013 5.65737C10.9718 5.65737 10.1263 4.98074 9.58028 4.02919L9.47439 3.84628C9.24726 3.46903 8.77471 3.35841 8.43026 3.56334L8.41711 3.57101L6.68711 4.56101C6.21226 4.83235 6.04765 5.44933 6.31938 5.91962L5.5401 6.36987L6.31875 5.91855C6.86983 6.86931 7.03394 7.94095 6.51988 8.83299C6.00591 9.72489 4.99685 10.1199 3.9001 10.1199C3.35023 10.1199 2.9001 10.5738 2.9001 11.1199V12.8799C2.9001 13.4259 3.35023 13.8799 3.9001 13.8799C4.99685 13.8799 6.00591 14.2748 6.51988 15.1667C7.03379 16.0585 6.86993 17.1298 6.31923 18.0804C6.04771 18.5506 6.21186 19.1672 6.68662 19.4384L8.41711 20.4287L8.43026 20.4364C8.7747 20.6413 9.24723 20.5307 9.47436 20.1535L9.58121 19.9689C10.1321 19.0186 10.98 18.3424 12.0088 18.3424C13.0384 18.3424 13.8838 19.019 14.4299 19.9704C14.4302 19.9711 14.4306 19.9717 14.4309 19.9723L14.5358 20.1535C14.7629 20.5307 15.2355 20.6413 15.5799 20.4364L15.5931 20.4287L17.3231 19.4387C17.7969 19.168 17.9647 18.5595 17.6887 18.0764L18.4701 17.6299L17.6914 18.0812C17.1404 17.1304 16.9763 16.0588 17.4903 15.1667C18.0043 14.2748 19.0134 13.8799 20.1101 13.8799C20.66 13.8799 21.1101 13.4259 21.1101 12.8799V11.1199C21.1101 10.57 20.6561 10.1199 20.1101 10.1199C19.0134 10.1199 18.0043 9.72489 17.4903 8.83299C16.9764 7.94123 17.1403 6.86998 17.6909 5.91945C17.9625 5.44918 17.7984 4.8326 17.3236 4.56129L15.5931 3.57101L15.5799 3.56334C15.2355 3.35841 14.7629 3.46903 14.5358 3.84629ZM16.493 2.01211C15.2597 1.28376 13.699 1.73233 12.9866 2.92971L12.8714 3.12855C12.8714 3.12855 12.8714 3.12855 12.8714 3.12855C12.5123 3.74817 12.165 3.85737 12.0013 3.85737C11.839 3.85737 11.4949 3.74982 11.1409 3.13228L11.029 2.93894L11.0236 2.92971C10.3112 1.73233 8.75046 1.28376 7.51717 2.01211L5.79357 2.99845C4.4488 3.76725 3.99263 5.49057 4.76082 6.82012L4.76144 6.82119C5.12036 7.44043 5.04125 7.7938 4.96031 7.93425C4.87929 8.07485 4.61335 8.31987 3.9001 8.31987C2.34997 8.31987 1.1001 9.5859 1.1001 11.1199V12.8799C1.1001 14.4138 2.34996 15.6799 3.9001 15.6799C4.61335 15.6799 4.87929 15.9249 4.96031 16.0655C5.04125 16.2059 5.12036 16.5593 4.76144 17.1785L4.76082 17.1796C3.99254 18.5093 4.44842 20.2326 5.79357 21.0013L7.5172 21.9876C8.75048 22.716 10.3112 22.2674 11.0236 21.07L11.1388 20.8712C11.1387 20.8713 11.1388 20.8711 11.1388 20.8712C11.4978 20.2519 11.8452 20.1424 12.0088 20.1424C12.1711 20.1424 12.5153 20.2499 12.8693 20.8675L12.8712 20.8708L12.9812 21.0608L12.9866 21.07C13.699 22.2674 15.2597 22.716 16.493 21.9876L18.2166 21.0013C19.5628 20.232 20.0155 18.5202 19.2515 17.1833L19.2488 17.1785C18.8898 16.5593 18.9689 16.2059 19.0499 16.0655C19.1309 15.9249 19.3968 15.6799 20.1101 15.6799C21.6602 15.6799 22.9101 14.4138 22.9101 12.8799V11.1199C22.9101 9.56974 21.6441 8.31987 20.1101 8.31987C19.3968 8.31987 19.1309 8.07485 19.0499 7.93425C18.9689 7.79379 18.8898 7.44043 19.2488 6.82119L19.2494 6.82012C20.0176 5.49057 19.5619 3.76753 18.2171 2.99873L16.493 2.01211Z" fill="#A5A5B3"/>
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 9.9001C11.3402 9.9001 10.4 10.8403 10.4 12.0001C10.4 13.1599 11.3402 14.1001 12.5 14.1001C13.6598 14.1001 14.6 13.1599 14.6 12.0001C14.6 10.8403 13.6598 9.9001 12.5 9.9001ZM8.59998 12.0001C8.59998 9.84619 10.3461 8.1001 12.5 8.1001C14.6539 8.1001 16.4 9.84619 16.4 12.0001C16.4 14.154 14.6539 15.9001 12.5 15.9001C10.3461 15.9001 8.59998 14.154 8.59998 12.0001Z" fill="#A5A5B3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.0357 3.84629L14.9289 4.03081C14.378 4.98117 13.53 5.65737 12.5012 5.65737C11.4716 5.65737 10.6262 4.98074 10.0802 4.02919L9.97426 3.84628C9.74714 3.46903 9.27458 3.35841 8.93014 3.56334L8.91699 3.57101L7.18699 4.56101C6.71214 4.83235 6.54753 5.44933 6.81925 5.91962L6.03998 6.36987L6.81863 5.91855C7.36971 6.86931 7.53382 7.94095 7.01976 8.83299C6.50578 9.72489 5.49672 10.1199 4.39998 10.1199C3.85011 10.1199 3.39998 10.5738 3.39998 11.1199V12.8799C3.39998 13.4259 3.85011 13.8799 4.39998 13.8799C5.49672 13.8799 6.50578 14.2748 7.01976 15.1667C7.53367 16.0585 7.3698 17.1298 6.81911 18.0804C6.54759 18.5506 6.71174 19.1672 7.1865 19.4384L8.91699 20.4287L8.93014 20.4364C9.27457 20.6413 9.74711 20.5307 9.97424 20.1535L10.0811 19.9689C10.6319 19.0186 11.4799 18.3424 12.5087 18.3424C13.5383 18.3424 14.3837 19.019 14.9297 19.9704C14.9301 19.9711 14.9304 19.9717 14.9308 19.9723L15.0357 20.1535C15.2628 20.5307 15.7354 20.6413 16.0798 20.4364L16.093 20.4287L17.823 19.4387C18.2967 19.168 18.4646 18.5595 18.1886 18.0764L18.97 17.6299L18.1913 18.0812C17.6402 17.1304 17.4761 16.0588 17.9902 15.1667C18.5042 14.2748 19.5132 13.8799 20.61 13.8799C21.1598 13.8799 21.61 13.4259 21.61 12.8799V11.1199C21.61 10.57 21.156 10.1199 20.61 10.1199C19.5132 10.1199 18.5042 9.72489 17.9902 8.83299C17.4763 7.94123 17.6401 6.86998 18.1908 5.91945C18.4624 5.44918 18.2982 4.8326 17.8235 4.56129L16.093 3.57101L16.0798 3.56334C15.7354 3.35841 15.2628 3.46903 15.0357 3.84629ZM16.9929 2.01211C15.7596 1.28376 14.1989 1.73233 13.4865 2.92971L13.3713 3.12855C13.3713 3.12855 13.3713 3.12855 13.3713 3.12855C13.0122 3.74817 12.6649 3.85737 12.5012 3.85737C12.3389 3.85737 11.9948 3.74982 11.6408 3.13228L11.5289 2.93894L11.5234 2.92971C10.8111 1.73233 9.25034 1.28376 8.01705 2.01211L6.29345 2.99845C4.94867 3.76725 4.49251 5.49057 5.2607 6.82012L5.26132 6.82119C5.62024 7.44043 5.54113 7.7938 5.46019 7.93425C5.37917 8.07485 5.11323 8.31987 4.39998 8.31987C2.84984 8.31987 1.59998 9.5859 1.59998 11.1199V12.8799C1.59998 14.4138 2.84984 15.6799 4.39998 15.6799C5.11323 15.6799 5.37917 15.9249 5.46019 16.0655C5.54113 16.2059 5.62024 16.5593 5.26132 17.1785L5.2607 17.1796C4.49242 18.5093 4.9483 20.2326 6.29345 21.0013L8.01707 21.9876C9.25036 22.716 10.8111 22.2674 11.5234 21.07L11.6386 20.8712C11.6386 20.8713 11.6387 20.8711 11.6386 20.8712C11.9977 20.2519 12.3451 20.1424 12.5087 20.1424C12.671 20.1424 13.0152 20.2499 13.3692 20.8675L13.3711 20.8708L13.4811 21.0608L13.4865 21.07C14.1989 22.2674 15.7596 22.716 16.9929 21.9876L18.7165 21.0013C20.0627 20.232 20.5153 18.5202 19.7514 17.1833L19.7486 17.1785C19.3897 16.5593 19.4688 16.2059 19.5498 16.0655C19.6308 15.9249 19.8967 15.6799 20.61 15.6799C22.1601 15.6799 23.41 14.4138 23.41 12.8799V11.1199C23.41 9.56974 22.1439 8.31987 20.61 8.31987C19.8967 8.31987 19.6308 8.07485 19.5498 7.93425C19.4688 7.79379 19.3897 7.44043 19.7486 6.82119L19.7493 6.82012C20.5174 5.49057 20.0618 3.76753 18.717 2.99873L16.9929 2.01211Z" fill="#A5A5B3"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -128,7 +128,7 @@ const cn = {
Settings: {
Title: "设置",
SubTitle: "所有设置选项",
GeneralSettings: "通用设置",
Danger: {
Reset: {
Title: "重置所有设置",

View File

@ -131,6 +131,7 @@ const en: LocaleType = {
Settings: {
Title: "Settings",
SubTitle: "All Settings",
GeneralSettings: "General settings",
Danger: {
Reset: {
Title: "Reset All Settings",

View File

@ -95,6 +95,7 @@ const jp: PartialLocaleType = {
Settings: {
Title: "設定",
SubTitle: "設定オプション",
GeneralSettings: "一般設定",
Danger: {
Reset: {
Title: "設定をリセット",

View File

@ -30,9 +30,26 @@ export type ChatMessage = RequestMessage & {
model?: ModelType;
};
export function createMessage(override: Partial<ChatMessage>): ChatMessage {
let tempGlobalId = 0;
export function createMessage(
override: Partial<ChatMessage>,
options?: { temp?: boolean; customId?: string },
): ChatMessage {
const { temp, customId } = options ?? {};
let id: string;
if (customId) {
id = customId;
} else if (temp) {
tempGlobalId += 1;
id = String(tempGlobalId);
} else {
id = nanoid();
}
return {
id: nanoid(),
id,
date: new Date().toLocaleString(),
role: "user",
content: "",

View File

@ -27,4 +27,5 @@ body {
:root {
--tip-popover-color: #434360;
--chat-panel-bg: rgb(249, 250, 251, 1);
--siderbar-mobile-height: 3.125rem;
}

View File

@ -11,6 +11,11 @@ module.exports = {
'sm-mobile-tab': '0.625rem',
'chat-header-title': '1rem',
'actions-popover-menu-item': '15px',
'setting-title': '1.25rem',
'setting-items': '1rem',
},
fontWeight: {
'setting-title': '700',
},
fontFamily: {
'common': ['Satoshi Variable', 'Variable'],
@ -23,10 +28,6 @@ module.exports = {
xl: '1440px',
'2xl': '1980px'
},
// spacing: Array.from({ length: 1000 }).reduce((map, _, index) => {
// map[index] = `${index}rem`;
// return map;
// }, {}),
extend: {
minHeight: {
'chat-input-mobile': '19px',
@ -41,11 +42,14 @@ module.exports = {
'actions-popover': '203px',
},
height: {
mobile: '3.125rem',
mobile: 'var(--siderbar-mobile-height)',
// mobile: '3.125rem',
'menu-title-mobile': '3rem',
'thumbnail': '5rem',
'chat-input-mobile': '19px',
'chat-input': '60px',
'chat-panel-mobile': '- var(--siderbar-mobile-height)',
'setting-panel-mobile': 'calc(100vh - var(--siderbar-mobile-height))',
},
flexBasis: {
'sidebar': 'var(--sidebar-width)',
@ -53,6 +57,7 @@ module.exports = {
},
spacing: {
'chat-header-gap': '0.625rem',
'chat-panel-mobile': 'var(--siderbar-mobile-height)'
},
backgroundImage: {
'message-bg': 'linear-gradient(259deg, #9786FF 8.42%, #4A5CFF 90.13%)',
@ -75,6 +80,7 @@ module.exports = {
'chat-input': '0px 4px 20px 0px rgba(60, 68, 255, 0.13)',
'actions-popover': '0px 14px 40px 0px rgba(0, 0, 0, 0.12)',
'actions-bar': '0px 4px 30px 0px rgba(0, 0, 0, 0.10)',
'prompt-hint-container': 'inset 0 4px 8px 0 rgba(0, 0, 0, 0.1)'
}
},
borderRadius: {