Merge branch 'feat-redesign-ui' into v3

This commit is contained in:
Fred 2024-05-08 14:24:43 +08:00
commit 00b1a9781d
No known key found for this signature in database
GPG Key ID: 4DABDA85EF70EC71
28 changed files with 186 additions and 72 deletions

View File

@ -42,7 +42,7 @@ export default function Btn(props: BtnProps) {
} text-text-btn-primary `; } text-text-btn-primary `;
break; break;
case "danger": case "danger":
btnClassName = `bg-danger-btn text-text-btn-danger hover:bg-hovered-btn`; btnClassName = `bg-danger-btn text-text-btn-danger hover:bg-hovered-danger-btn`;
break; break;
default: default:
btnClassName = `bg-default-btn text-text-btn-default hover:bg-hovered-btn`; btnClassName = `bg-default-btn text-text-btn-default hover:bg-hovered-btn`;

View File

@ -4,7 +4,6 @@ import {
DetailedHTMLProps, DetailedHTMLProps,
InputHTMLAttributes, InputHTMLAttributes,
useContext, useContext,
useEffect,
useLayoutEffect, useLayoutEffect,
useState, useState,
} from "react"; } from "react";
@ -17,6 +16,7 @@ export interface CommonInputProps
> { > {
className?: string; className?: string;
} }
export interface NumberInputProps { export interface NumberInputProps {
onChange?: (v: number) => void; onChange?: (v: number) => void;
type?: "number"; type?: "number";
@ -49,12 +49,16 @@ export default function Input(props: CommonInputProps & InputProps) {
const internalType = (show && "text") || type; const internalType = (show && "text") || type;
const { update } = useContext(List.ListContext); const { update, handleValidate } = useContext(List.ListContext);
useLayoutEffect(() => { useLayoutEffect(() => {
update?.({ type: "input" }); update?.({ type: "input" });
}, []); }, []);
useLayoutEffect(() => {
handleValidate?.(value);
}, [value]);
return ( return (
<div <div
className={` group/input w-[100%] rounded-chat-input bg-input transition-colors duration-300 ease-in-out flex gap-3 items-center px-3 py-2 ${className} hover:bg-select-hover ${inputClassName}`} className={` group/input w-[100%] rounded-chat-input bg-input transition-colors duration-300 ease-in-out flex gap-3 items-center px-3 py-2 ${className} hover:bg-select-hover ${inputClassName}`}

View File

@ -17,6 +17,7 @@ interface WidgetStyle {
interface ChildrenMeta { interface ChildrenMeta {
type?: "unknown" | "input" | "range"; type?: "unknown" | "input" | "range";
error?: string;
} }
export interface ListProps { export interface ListProps {
@ -27,6 +28,15 @@ export interface ListProps {
widgetStyle?: WidgetStyle; widgetStyle?: WidgetStyle;
} }
type Error =
| {
error: true;
message: string;
}
| {
error: false;
};
export interface ListItemProps { export interface ListItemProps {
title: string; title: string;
subTitle?: string; subTitle?: string;
@ -34,10 +44,15 @@ export interface ListItemProps {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
nextline?: boolean; nextline?: boolean;
validator?: (v: any) => Error | Promise<Error>;
} }
export const ListContext = createContext< export const ListContext = createContext<
{ isMobileScreen?: boolean; update?: (m: ChildrenMeta) => void } & WidgetStyle {
isMobileScreen?: boolean;
update?: (m: ChildrenMeta) => void;
handleValidate?: (v: any) => void;
} & WidgetStyle
>({ isMobileScreen: false }); >({ isMobileScreen: false });
export function ListItem(props: ListItemProps) { export function ListItem(props: ListItemProps) {
@ -48,6 +63,7 @@ export function ListItem(props: ListItemProps) {
subTitle, subTitle,
children, children,
nextline, nextline,
validator,
} = props; } = props;
const context = useContext(ListContext); const context = useContext(ListContext);
@ -56,9 +72,11 @@ export function ListItem(props: ListItemProps) {
const { inputNextLine, rangeNextLine } = context; const { inputNextLine, rangeNextLine } = context;
const { type, error } = childrenMeta;
let internalNextLine; let internalNextLine;
switch (childrenMeta.type) { switch (type) {
case "input": case "input":
internalNextLine = !!(nextline || inputNextLine); internalNextLine = !!(nextline || inputNextLine);
break; break;
@ -70,7 +88,22 @@ export function ListItem(props: ListItemProps) {
} }
const update = useCallback((m: ChildrenMeta) => { const update = useCallback((m: ChildrenMeta) => {
setMeta(m); setMeta((pre) => ({ ...pre, ...m }));
}, []);
const handleValidate = useCallback((v: any) => {
const insideValidator = validator || (() => {});
Promise.resolve(insideValidator(v)).then((result) => {
if (result && result.error) {
return update({
error: result.message,
});
}
update({
error: undefined,
});
});
}, []); }, []);
return ( return (
@ -88,13 +121,18 @@ export function ListItem(props: ListItemProps) {
<div className={` text-sm text-text-list-subtitle`}>{subTitle}</div> <div className={` text-sm text-text-list-subtitle`}>{subTitle}</div>
)} )}
</div> </div>
<ListContext.Provider value={{ ...context, update }}> <ListContext.Provider value={{ ...context, update, handleValidate }}>
<div <div
className={`${ className={`${
internalNextLine ? "mt-[0.625rem]" : "max-w-[70%]" internalNextLine ? "mt-[0.625rem]" : "max-w-[70%]"
} flex items-center justify-center`} } flex flex-col items-center justify-center`}
> >
{children} <div>{children}</div>
{!!error && (
<div className="text-text-btn-danger text-sm-mobile-tab mt-[0.3125rem] flex items-start w-[100%]">
<div className="">{error}</div>
</div>
)}
</div> </div>
</ListContext.Provider> </ListContext.Provider>
</div> </div>

View File

@ -61,6 +61,7 @@ export default function MenuLayout<
<div <div
className={` className={`
w-[100%] relative bg-center w-[100%] relative bg-center
max-md:h-[100%]
md:flex md:my-2.5 md:flex md:my-2.5
`} `}
> >

View File

@ -96,7 +96,7 @@ const Select = <Value extends number | string>(props: SearchProps<Value>) => {
className={selectClassName} className={selectClassName}
> >
<div <div
className={`flex items-center gap-3 py-2 px-3 bg-select rounded-action-btn font-time text-sm-title cursor-pointer hover:bg-select-hover transition duration-300 ease-in-out`} className={`flex items-center gap-3 py-2 px-3 bg-select rounded-action-btn font-common text-sm-title cursor-pointer hover:bg-select-hover transition duration-300 ease-in-out`}
ref={contentRef} ref={contentRef}
> >
<div <div

View File

@ -2,6 +2,9 @@
&-body { &-body {
margin-top: 20px; margin-top: 20px;
} }
div:not(.no-dark) > svg {
filter: invert(0.5);
}
} }
.export-content { .export-content {

View File

@ -4,6 +4,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
div:not(.no-dark) > svg {
filter: invert(0.5);
}
.mask-page-body { .mask-page-body {
padding: 20px; padding: 20px;
overflow-y: auto; overflow-y: auto;

View File

@ -398,7 +398,7 @@ export function ContextPrompts(props: {
); );
} }
export function MaskPage() { export function MaskPage(props: { className?: string }) {
const navigate = useNavigate(); const navigate = useNavigate();
const maskStore = useMaskStore(); const maskStore = useMaskStore();
@ -465,14 +465,13 @@ export function MaskPage() {
}); });
}; };
const isMobileScreen = useMobileScreen();
return ( return (
<> <>
<div <div
className={`${styles["mask-page"]} !bg-gray-50 ${ className={`
isMobileScreen ? "pb-chat-panel-mobile" : "" ${styles["mask-page"]}
}`} ${props.className}
`}
> >
<div className="window-header"> <div className="window-header">
<div className="window-header-title"> <div className="window-header-title">

View File

@ -8,6 +8,10 @@
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
div:not(.no-dark) > svg {
filter: invert(0.5);
}
.mask-header { .mask-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -72,7 +72,7 @@ function useMaskGroup(masks: Mask[]) {
return groups; return groups;
} }
export function NewChat() { export function NewChat(props: { className?: string }) {
const chatStore = useChatStore(); const chatStore = useChatStore();
const maskStore = useMaskStore(); const maskStore = useMaskStore();
@ -115,9 +115,10 @@ export function NewChat() {
return ( return (
<div <div
className={`${styles["new-chat"]} !bg-gray-50 px-1 ${ className={`
isMobileScreen ? "pb-chat-panel-mobile" : "" ${styles["new-chat"]}
}`} ${props.className}
`}
> >
<div className={styles["mask-header"]}> <div className={styles["mask-header"]}>
<IconButton <IconButton

View File

@ -301,7 +301,7 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
{!isMobileScreen && ( {!isMobileScreen && (
<div className="flex items-center justify-center text-sm gap-3"> <div className="flex items-center justify-center text-sm gap-3">
<div className="flex-1">&nbsp;</div> <div className="flex-1">&nbsp;</div>
<div className="text-text-chat-input-placeholder text-time line-clamp-1"> <div className="text-text-chat-input-placeholder font-common line-clamp-1">
{Locale.Chat.Input(submitKey)} {Locale.Chat.Input(submitKey)}
</div> </div>
<Btn <Btn

View File

@ -185,7 +185,7 @@ export default function ChatMessagePanel(props: ChatMessagePanelProps) {
}`} }`}
> >
<div <div
className={` pointer-events-none text-text-chat-message-date text-right text-time whitespace-nowrap transition-all duration-500 text-sm absolute z-1 ${ className={` pointer-events-none text-text-chat-message-date text-right font-common whitespace-nowrap transition-all duration-500 text-sm absolute z-1 ${
isUser ? "right-0" : "left-0" isUser ? "right-0" : "left-0"
} bottom-[100%] hidden group-hover:block`} } bottom-[100%] hidden group-hover:block`}
> >

View File

@ -124,13 +124,17 @@ const ModelSelect = () => {
return ( return (
<Popover <Popover
content={content({ close: () => {} })} content={
<div className="max-h-chat-actions-select-model-popover overflow-y-auto">
{content({ close: () => {} })}
</div>
}
trigger="click" trigger="click"
noArrow noArrow
placement={ placement={
position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt" position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt"
} }
popoverClassName="border border-select-popover rounded-lg shadow-select-popover-shadow w-actions-popover bg-model-select-popover-panel max-h-chat-actions-select-model-popover w-[280px]" popoverClassName="border border-select-popover rounded-lg shadow-select-popover-shadow w-actions-popover bg-model-select-popover-panel w-[280px]"
onShow={(e) => { onShow={(e) => {
if (e) { if (e) {
autoScrollToSelectedModal(); autoScrollToSelectedModal();

View File

@ -67,15 +67,14 @@ export default function PromptHints(props: {
return ( return (
<div <div
className={` className={`
${styles["prompt-hints"]} transition-all duration-300 shadow-prompt-hint-container rounded-none flex flex-col-reverse overflow-x-hidden
transition-all duration-300 shadow-prompt-hint-container rounded-none w-[100%] flex flex-col-reverse overflow-auto ${
${ notShowPrompt
notShowPrompt ? "max-h-[0vh] border-none"
? "max-h-[0vh] border-none" : "border-b pt-2.5 max-h-[50vh]"
: "border-b pt-2.5 max-h-[50vh]" }
} ${props.className}
${props.className} `}
`}
> >
{internalPrompts.map((prompt, i) => ( {internalPrompts.map((prompt, i) => (
<div <div

View File

@ -525,38 +525,33 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.prompt-hints { .prompt-hint {
.prompt-hint { color:var(--btn-default-text);
color:var(--btn-default-text); padding: 6px 10px;
padding: 6px 10px; border: transparent 1px solid;
// animation: slide-in ease 0.3s; margin: 4px;
// cursor: pointer; border-radius: 8px;
// transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;
&:not(:last-child) { &:not(:last-child) {
margin-top: 0; margin-top: 0;
} }
.hint-title { .hint-title {
font-size: 12px; font-size: 12px;
font-weight: bolder; font-weight: bolder;
@include single-line(); @include single-line();
} }
.hint-content { .hint-content {
font-size: 12px; font-size: 12px;
@include single-line(); @include single-line();
} }
&-selected, &-selected,
&:hover { &:hover {
border-color: var(--primary); border-color: var(--primary);
}
} }
} }

View File

@ -153,6 +153,16 @@ export default function ModelSetting(props: {
title={Locale.Settings.InputTemplate.Title} title={Locale.Settings.InputTemplate.Title}
subTitle={Locale.Settings.InputTemplate.SubTitle} subTitle={Locale.Settings.InputTemplate.SubTitle}
nextline={isMobileScreen} nextline={isMobileScreen}
validator={(v: string) => {
if (!v.includes("{{input}}")) {
return {
error: true,
message: Locale.Settings.InputTemplate.Error,
};
}
return { error: false };
}}
> >
<Input <Input
type="text" type="text"
@ -160,7 +170,6 @@ export default function ModelSetting(props: {
onChange={(e = "") => onChange={(e = "") =>
props.updateConfig((config) => (config.template = e)) props.updateConfig((config) => (config.template = e))
} }
className="text-center"
></Input> ></Input>
</ListItem> </ListItem>
</> </>

View File

@ -71,15 +71,13 @@ export default MenuLayout(function SettingList(props) {
cursor-pointer cursor-pointer
border border
rounded-md rounded-md
border-transparent
bg-chat-menu-session-unselected border-chat-menu-session-unselected
${ ${
selected === i.id && !isMobileScreen selected === i.id && !isMobileScreen
? `!bg-chat-menu-session-selected !border-chat-menu-session-selected !font-medium` ? `!bg-chat-menu-session-selected !border-chat-menu-session-selected !font-medium`
: `hover:bg-chat-menu-session-hovered hover:chat-menu-session-hovered` : `hover:bg-chat-menu-session-unselected hover:border-chat-menu-session-unselected`
} }
hover:border-opacity-100 hover:font-semibold hover:bg-settings-menu-item-selected
flex justify-between items-center flex justify-between items-center
max-md:bg-settings-menu-item-mobile max-md:bg-settings-menu-item-mobile
`} `}

View File

@ -111,8 +111,30 @@ export default function Home() {
<ErrorBoundary> <ErrorBoundary>
<Routes> <Routes>
<Route path={Path.Home} element={<Chat />} /> <Route path={Path.Home} element={<Chat />} />
<Route path={Path.NewChat} element={<NewChat />} /> <Route
<Route path={Path.Masks} element={<MaskPage />} /> path={Path.NewChat}
element={
<NewChat
className={`
md:w-[100%] px-1
${config.theme === "dark" ? "bg-[var(--white)]" : "bg-gray-50"}
${config.isMobileScreen ? "pb-chat-panel-mobile" : ""}
`}
/>
}
/>
<Route
path={Path.Masks}
element={
<MaskPage
className={`
md:w-[100%]
${config.theme === "dark" ? "bg-[var(--white)]" : "bg-gray-50"}
${config.isMobileScreen ? "pb-chat-panel-mobile" : ""}
`}
/>
}
/>
<Route path={Path.Chat} element={<Chat />} /> <Route path={Path.Chat} element={<Chat />} />
<Route path={Path.Settings} element={<Settings />} /> <Route path={Path.Settings} element={<Settings />} />
</Routes> </Routes>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
import { useLayoutEffect } from "react"; import { useLayoutEffect } from "react";
import { Theme, useAppConfig } from "@/app/store/config"; import { Theme, useAppConfig } from "@/app/store/config";
import { getCSSVar } from "../utils";
const DARK_CLASS = "dark-new"; const DARK_CLASS = "dark-new";
const LIGHT_CLASS = "light-new"; const LIGHT_CLASS = "light-new";
@ -17,4 +18,31 @@ export function useSwitchTheme() {
document.body.classList.add(LIGHT_CLASS); document.body.classList.add(LIGHT_CLASS);
} }
}, [config.theme]); }, [config.theme]);
useLayoutEffect(() => {
document.body.classList.remove("light");
document.body.classList.remove("dark");
if (config.theme === "dark") {
document.body.classList.add("dark");
} else if (config.theme === "light") {
document.body.classList.add("light");
}
const metaDescriptionDark = document.querySelector(
'meta[name="theme-color"][media*="dark"]',
);
const metaDescriptionLight = document.querySelector(
'meta[name="theme-color"][media*="light"]',
);
if (config.theme === "auto") {
metaDescriptionDark?.setAttribute("content", "#151515");
metaDescriptionLight?.setAttribute("content", "#fafafa");
} else {
const themeColor = getCSSVar("--theme-color");
metaDescriptionDark?.setAttribute("content", themeColor);
metaDescriptionLight?.setAttribute("content", themeColor);
}
}, [config.theme]);
} }

View File

@ -170,6 +170,7 @@ const cn = {
InputTemplate: { InputTemplate: {
Title: "用户输入预处理", Title: "用户输入预处理",
SubTitle: "用户最新的一条消息会填充到此模板", SubTitle: "用户最新的一条消息会填充到此模板",
Error: "模板中必须携带占位符{{input}}",
}, },
Update: { Update: {

View File

@ -174,6 +174,7 @@ const en: LocaleType = {
InputTemplate: { InputTemplate: {
Title: "Input Template", Title: "Input Template",
SubTitle: "Newest message will be filled to this template", SubTitle: "Newest message will be filled to this template",
Error: "Placeholder {{input}} must be included in the template",
}, },
Update: { Update: {

View File

@ -64,6 +64,7 @@ body {
--danger-btn-bg: #fff6f6; --danger-btn-bg: #fff6f6;
--default-btn-bg: #f7f7f8; --default-btn-bg: #f7f7f8;
--hovered-btn-bg: rgba(0, 0, 0, 0.05); --hovered-btn-bg: rgba(0, 0, 0, 0.05);
--hovered-danger-btn-bg: #FFE7E7;
--card-bg: #fff; --card-bg: #fff;
--input-bg: #f7f7f8; --input-bg: #f7f7f8;
--list-item-divider-bg: #f0f0f3; --list-item-divider-bg: #f0f0f3;
@ -191,6 +192,7 @@ body {
--danger-btn-bg: #20131A; --danger-btn-bg: #20131A;
--default-btn-bg: #1D1D1D; --default-btn-bg: #1D1D1D;
--hovered-btn-bg: #303030; --hovered-btn-bg: #303030;
--hovered-danger-btn-bg:#303030;
--card-bg: #111; --card-bg: #111;
--input-bg: #1D1D1D; --input-bg: #1D1D1D;
--list-item-divider-bg: #303030; --list-item-divider-bg: #303030;
@ -216,7 +218,7 @@ body {
--chat-panel-message-mobile-bg: #303030; --chat-panel-message-mobile-bg: #303030;
--chat-message-actions-btn-hovered-bg: rgba(255, 255, 255, 0.05); --chat-message-actions-btn-hovered-bg: rgba(255, 255, 255, 0.05);
--chat-panel-bg: #1D1D1D; --chat-panel-bg: #1D1D1D;
--chat-panel-message-clear-divider-bg: #e2e2e6; ////////// --chat-panel-message-clear-divider-bg: #a5a5b3;
--chat-menu-session-selected-bg: #182455; --chat-menu-session-selected-bg: #182455;
--chat-menu-session-unselected-bg: #303030; --chat-menu-session-unselected-bg: #303030;
--chat-menu-session-hovered-bg: #404040; --chat-menu-session-hovered-bg: #404040;

View File

@ -13,6 +13,9 @@
font-feature-settings: font-feature-settings:
"clig" off, "clig" off,
"liga" off; "liga" off;
src: url(/fonts/Roboto.woff2) format("woff2"); src:
url("../fonts/Satoshi-Variable.woff2") format("woff2"),
url("../fonts/Satoshi-Variable.woff") format("woff"),
url("../fonts/Satoshi-Variable.ttf") format("truetype");
} }
} }

View File

@ -38,10 +38,6 @@
--border-in-light: 1px solid rgba(255, 255, 255, 0.192); --border-in-light: 1px solid rgba(255, 255, 255, 0.192);
--theme-color: var(--gray); --theme-color: var(--gray);
div:not(.no-dark) > svg {
filter: invert(0.5);
}
} }
.light { .light {

View File

@ -90,6 +90,7 @@ module.exports = {
'danger-btn': 'var(--danger-btn-bg)', 'danger-btn': 'var(--danger-btn-bg)',
'default-btn': 'var(--default-btn-bg)', 'default-btn': 'var(--default-btn-bg)',
'hovered-btn': 'var(--hovered-btn-bg)', 'hovered-btn': 'var(--hovered-btn-bg)',
'hovered-danger-btn': 'var(--hovered-danger-btn-bg)',
'card': 'var(--card-bg)', 'card': 'var(--card-bg)',
'input': 'var(--input-bg)', 'input': 'var(--input-bg)',
'list-item-divider': 'var(--list-item-divider-bg)', 'list-item-divider': 'var(--list-item-divider-bg)',
@ -186,6 +187,7 @@ module.exports = {
'chat-menu-session-unselected-mobile': 'var(--chat-menu-session-unselected-mobile-border)', 'chat-menu-session-unselected-mobile': 'var(--chat-menu-session-unselected-mobile-border)',
'chat-menu-session-hovered': 'var(--chat-menu-session-hovered-border)', 'chat-menu-session-hovered': 'var(--chat-menu-session-hovered-border)',
'modal-header-bottom': 'var(--modal-header-bottom-border)', 'modal-header-bottom': 'var(--modal-header-bottom-border)',
'transparent': 'transparent',
'text-sidebar-tab-mobile-active': 'var(--sidebar-tab-mobile-active-text)', 'text-sidebar-tab-mobile-active': 'var(--sidebar-tab-mobile-active-text)',
'text-sidebar-tab-mobile-inactive': 'var(--sidebar-tab-mobile-inactive-text)', 'text-sidebar-tab-mobile-inactive': 'var(--sidebar-tab-mobile-inactive-text)',