Merge branch 'feat-redesign-ui' into v3
This commit is contained in:
commit
00b1a9781d
|
@ -42,7 +42,7 @@ export default function Btn(props: BtnProps) {
|
|||
} text-text-btn-primary `;
|
||||
break;
|
||||
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;
|
||||
default:
|
||||
btnClassName = `bg-default-btn text-text-btn-default hover:bg-hovered-btn`;
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
DetailedHTMLProps,
|
||||
InputHTMLAttributes,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
@ -17,6 +16,7 @@ export interface CommonInputProps
|
|||
> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface NumberInputProps {
|
||||
onChange?: (v: number) => void;
|
||||
type?: "number";
|
||||
|
@ -49,12 +49,16 @@ export default function Input(props: CommonInputProps & InputProps) {
|
|||
|
||||
const internalType = (show && "text") || type;
|
||||
|
||||
const { update } = useContext(List.ListContext);
|
||||
const { update, handleValidate } = useContext(List.ListContext);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
update?.({ type: "input" });
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
handleValidate?.(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<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}`}
|
||||
|
|
|
@ -17,6 +17,7 @@ interface WidgetStyle {
|
|||
|
||||
interface ChildrenMeta {
|
||||
type?: "unknown" | "input" | "range";
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ListProps {
|
||||
|
@ -27,6 +28,15 @@ export interface ListProps {
|
|||
widgetStyle?: WidgetStyle;
|
||||
}
|
||||
|
||||
type Error =
|
||||
| {
|
||||
error: true;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
error: false;
|
||||
};
|
||||
|
||||
export interface ListItemProps {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
|
@ -34,10 +44,15 @@ export interface ListItemProps {
|
|||
className?: string;
|
||||
onClick?: () => void;
|
||||
nextline?: boolean;
|
||||
validator?: (v: any) => Error | Promise<Error>;
|
||||
}
|
||||
|
||||
export const ListContext = createContext<
|
||||
{ isMobileScreen?: boolean; update?: (m: ChildrenMeta) => void } & WidgetStyle
|
||||
{
|
||||
isMobileScreen?: boolean;
|
||||
update?: (m: ChildrenMeta) => void;
|
||||
handleValidate?: (v: any) => void;
|
||||
} & WidgetStyle
|
||||
>({ isMobileScreen: false });
|
||||
|
||||
export function ListItem(props: ListItemProps) {
|
||||
|
@ -48,6 +63,7 @@ export function ListItem(props: ListItemProps) {
|
|||
subTitle,
|
||||
children,
|
||||
nextline,
|
||||
validator,
|
||||
} = props;
|
||||
|
||||
const context = useContext(ListContext);
|
||||
|
@ -56,9 +72,11 @@ export function ListItem(props: ListItemProps) {
|
|||
|
||||
const { inputNextLine, rangeNextLine } = context;
|
||||
|
||||
const { type, error } = childrenMeta;
|
||||
|
||||
let internalNextLine;
|
||||
|
||||
switch (childrenMeta.type) {
|
||||
switch (type) {
|
||||
case "input":
|
||||
internalNextLine = !!(nextline || inputNextLine);
|
||||
break;
|
||||
|
@ -70,7 +88,22 @@ export function ListItem(props: ListItemProps) {
|
|||
}
|
||||
|
||||
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 (
|
||||
|
@ -88,13 +121,18 @@ export function ListItem(props: ListItemProps) {
|
|||
<div className={` text-sm text-text-list-subtitle`}>{subTitle}</div>
|
||||
)}
|
||||
</div>
|
||||
<ListContext.Provider value={{ ...context, update }}>
|
||||
<ListContext.Provider value={{ ...context, update, handleValidate }}>
|
||||
<div
|
||||
className={`${
|
||||
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>
|
||||
</ListContext.Provider>
|
||||
</div>
|
||||
|
|
|
@ -61,6 +61,7 @@ export default function MenuLayout<
|
|||
<div
|
||||
className={`
|
||||
w-[100%] relative bg-center
|
||||
max-md:h-[100%]
|
||||
md:flex md:my-2.5
|
||||
`}
|
||||
>
|
||||
|
|
|
@ -96,7 +96,7 @@ const Select = <Value extends number | string>(props: SearchProps<Value>) => {
|
|||
className={selectClassName}
|
||||
>
|
||||
<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}
|
||||
>
|
||||
<div
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
&-body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
div:not(.no-dark) > svg {
|
||||
filter: invert(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.export-content {
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
div:not(.no-dark) > svg {
|
||||
filter: invert(0.5);
|
||||
}
|
||||
|
||||
.mask-page-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -398,7 +398,7 @@ export function ContextPrompts(props: {
|
|||
);
|
||||
}
|
||||
|
||||
export function MaskPage() {
|
||||
export function MaskPage(props: { className?: string }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const maskStore = useMaskStore();
|
||||
|
@ -465,14 +465,13 @@ export function MaskPage() {
|
|||
});
|
||||
};
|
||||
|
||||
const isMobileScreen = useMobileScreen();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${styles["mask-page"]} !bg-gray-50 ${
|
||||
isMobileScreen ? "pb-chat-panel-mobile" : ""
|
||||
}`}
|
||||
className={`
|
||||
${styles["mask-page"]}
|
||||
${props.className}
|
||||
`}
|
||||
>
|
||||
<div className="window-header">
|
||||
<div className="window-header-title">
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
div:not(.no-dark) > svg {
|
||||
filter: invert(0.5);
|
||||
}
|
||||
|
||||
.mask-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -72,7 +72,7 @@ function useMaskGroup(masks: Mask[]) {
|
|||
return groups;
|
||||
}
|
||||
|
||||
export function NewChat() {
|
||||
export function NewChat(props: { className?: string }) {
|
||||
const chatStore = useChatStore();
|
||||
const maskStore = useMaskStore();
|
||||
|
||||
|
@ -115,9 +115,10 @@ export function NewChat() {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`${styles["new-chat"]} !bg-gray-50 px-1 ${
|
||||
isMobileScreen ? "pb-chat-panel-mobile" : ""
|
||||
}`}
|
||||
className={`
|
||||
${styles["new-chat"]}
|
||||
${props.className}
|
||||
`}
|
||||
>
|
||||
<div className={styles["mask-header"]}>
|
||||
<IconButton
|
||||
|
|
|
@ -301,7 +301,7 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
|
|||
{!isMobileScreen && (
|
||||
<div className="flex items-center justify-center text-sm gap-3">
|
||||
<div className="flex-1"> </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)}
|
||||
</div>
|
||||
<Btn
|
||||
|
|
|
@ -185,7 +185,7 @@ export default function ChatMessagePanel(props: ChatMessagePanelProps) {
|
|||
}`}
|
||||
>
|
||||
<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"
|
||||
} bottom-[100%] hidden group-hover:block`}
|
||||
>
|
||||
|
|
|
@ -124,13 +124,17 @@ const ModelSelect = () => {
|
|||
|
||||
return (
|
||||
<Popover
|
||||
content={content({ close: () => {} })}
|
||||
content={
|
||||
<div className="max-h-chat-actions-select-model-popover overflow-y-auto">
|
||||
{content({ close: () => {} })}
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
noArrow
|
||||
placement={
|
||||
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) => {
|
||||
if (e) {
|
||||
autoScrollToSelectedModal();
|
||||
|
|
|
@ -67,8 +67,7 @@ export default function PromptHints(props: {
|
|||
return (
|
||||
<div
|
||||
className={`
|
||||
${styles["prompt-hints"]}
|
||||
transition-all duration-300 shadow-prompt-hint-container rounded-none w-[100%] flex flex-col-reverse overflow-auto
|
||||
transition-all duration-300 shadow-prompt-hint-container rounded-none flex flex-col-reverse overflow-x-hidden
|
||||
${
|
||||
notShowPrompt
|
||||
? "max-h-[0vh] border-none"
|
||||
|
|
|
@ -525,13 +525,9 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.prompt-hints {
|
||||
.prompt-hint {
|
||||
.prompt-hint {
|
||||
color:var(--btn-default-text);
|
||||
padding: 6px 10px;
|
||||
// animation: slide-in ease 0.3s;
|
||||
// cursor: pointer;
|
||||
// transition: all ease 0.3s;
|
||||
border: transparent 1px solid;
|
||||
margin: 4px;
|
||||
border-radius: 8px;
|
||||
|
@ -557,7 +553,6 @@
|
|||
&:hover {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .chat-input-panel-inner {
|
||||
|
|
|
@ -153,6 +153,16 @@ export default function ModelSetting(props: {
|
|||
title={Locale.Settings.InputTemplate.Title}
|
||||
subTitle={Locale.Settings.InputTemplate.SubTitle}
|
||||
nextline={isMobileScreen}
|
||||
validator={(v: string) => {
|
||||
if (!v.includes("{{input}}")) {
|
||||
return {
|
||||
error: true,
|
||||
message: Locale.Settings.InputTemplate.Error,
|
||||
};
|
||||
}
|
||||
|
||||
return { error: false };
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
|
@ -160,7 +170,6 @@ export default function ModelSetting(props: {
|
|||
onChange={(e = "") =>
|
||||
props.updateConfig((config) => (config.template = e))
|
||||
}
|
||||
className="text-center"
|
||||
></Input>
|
||||
</ListItem>
|
||||
</>
|
||||
|
|
|
@ -71,15 +71,13 @@ export default MenuLayout(function SettingList(props) {
|
|||
cursor-pointer
|
||||
border
|
||||
rounded-md
|
||||
|
||||
bg-chat-menu-session-unselected border-chat-menu-session-unselected
|
||||
border-transparent
|
||||
${
|
||||
selected === i.id && !isMobileScreen
|
||||
? `!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
|
||||
max-md:bg-settings-menu-item-mobile
|
||||
`}
|
||||
|
|
|
@ -111,8 +111,30 @@ export default function Home() {
|
|||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<Route path={Path.Home} element={<Chat />} />
|
||||
<Route path={Path.NewChat} element={<NewChat />} />
|
||||
<Route path={Path.Masks} element={<MaskPage />} />
|
||||
<Route
|
||||
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.Settings} element={<Settings />} />
|
||||
</Routes>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
import { useLayoutEffect } from "react";
|
||||
import { Theme, useAppConfig } from "@/app/store/config";
|
||||
import { getCSSVar } from "../utils";
|
||||
|
||||
const DARK_CLASS = "dark-new";
|
||||
const LIGHT_CLASS = "light-new";
|
||||
|
@ -17,4 +18,31 @@ export function useSwitchTheme() {
|
|||
document.body.classList.add(LIGHT_CLASS);
|
||||
}
|
||||
}, [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]);
|
||||
}
|
||||
|
|
|
@ -170,6 +170,7 @@ const cn = {
|
|||
InputTemplate: {
|
||||
Title: "用户输入预处理",
|
||||
SubTitle: "用户最新的一条消息会填充到此模板",
|
||||
Error: "模板中必须携带占位符{{input}}",
|
||||
},
|
||||
|
||||
Update: {
|
||||
|
|
|
@ -174,6 +174,7 @@ const en: LocaleType = {
|
|||
InputTemplate: {
|
||||
Title: "Input Template",
|
||||
SubTitle: "Newest message will be filled to this template",
|
||||
Error: "Placeholder {{input}} must be included in the template",
|
||||
},
|
||||
|
||||
Update: {
|
||||
|
|
|
@ -64,6 +64,7 @@ body {
|
|||
--danger-btn-bg: #fff6f6;
|
||||
--default-btn-bg: #f7f7f8;
|
||||
--hovered-btn-bg: rgba(0, 0, 0, 0.05);
|
||||
--hovered-danger-btn-bg: #FFE7E7;
|
||||
--card-bg: #fff;
|
||||
--input-bg: #f7f7f8;
|
||||
--list-item-divider-bg: #f0f0f3;
|
||||
|
@ -191,6 +192,7 @@ body {
|
|||
--danger-btn-bg: #20131A;
|
||||
--default-btn-bg: #1D1D1D;
|
||||
--hovered-btn-bg: #303030;
|
||||
--hovered-danger-btn-bg:#303030;
|
||||
--card-bg: #111;
|
||||
--input-bg: #1D1D1D;
|
||||
--list-item-divider-bg: #303030;
|
||||
|
@ -216,7 +218,7 @@ body {
|
|||
--chat-panel-message-mobile-bg: #303030;
|
||||
--chat-message-actions-btn-hovered-bg: rgba(255, 255, 255, 0.05);
|
||||
--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-unselected-bg: #303030;
|
||||
--chat-menu-session-hovered-bg: #404040;
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
font-feature-settings:
|
||||
"clig" 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,10 +38,6 @@
|
|||
--border-in-light: 1px solid rgba(255, 255, 255, 0.192);
|
||||
|
||||
--theme-color: var(--gray);
|
||||
|
||||
div:not(.no-dark) > svg {
|
||||
filter: invert(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.light {
|
||||
|
|
|
@ -90,6 +90,7 @@ module.exports = {
|
|||
'danger-btn': 'var(--danger-btn-bg)',
|
||||
'default-btn': 'var(--default-btn-bg)',
|
||||
'hovered-btn': 'var(--hovered-btn-bg)',
|
||||
'hovered-danger-btn': 'var(--hovered-danger-btn-bg)',
|
||||
'card': 'var(--card-bg)',
|
||||
'input': 'var(--input-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-hovered': 'var(--chat-menu-session-hovered-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-inactive': 'var(--sidebar-tab-mobile-inactive-text)',
|
||||
|
|
Loading…
Reference in New Issue