Merge pull request #1658 from Yidadaa/bugfix-0520

feat: scrollable mask lists in new-chat page
This commit is contained in:
Yifei Zhang 2023-05-21 00:07:42 +08:00 committed by GitHub
commit a57fa2e9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 86 deletions

View File

@ -30,26 +30,30 @@ export async function requestOpenai(req: NextRequest) {
controller.abort();
}, 10 * 60 * 1000);
const fetchUrl = `${baseUrl}/${openaiPath}`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
Authorization: authValue,
...(process.env.OPENAI_ORG_ID && {
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
}),
},
cache: "no-store",
method: req.method,
body: req.body,
signal: controller.signal,
};
try {
return await fetch(`${baseUrl}/${openaiPath}`, {
headers: {
"Content-Type": "application/json",
Authorization: authValue,
...(process.env.OPENAI_ORG_ID && {
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
}),
},
cache: "no-store",
method: req.method,
body: req.body,
signal: controller.signal,
});
} catch (err: unknown) {
if (err instanceof Error && err.name === "AbortError") {
console.log("Fetch aborted");
} else {
throw err;
const res = await fetch(fetchUrl, fetchOptions);
if (res.status === 401) {
// to prevent browser prompt for credentials
res.headers.delete("www-authenticate");
}
return res;
} finally {
clearTimeout(timeoutId);
}

View File

@ -62,6 +62,7 @@ export function getHeaders() {
const accessStore = useAccessStore.getState();
let headers: Record<string, string> = {
"Content-Type": "application/json",
"x-requested-with": "XMLHttpRequest",
};
const makeBearer = (token: string) => `Bearer ${token.trim()}`;

View File

@ -54,13 +54,13 @@
.actions {
margin-top: 5vh;
margin-bottom: 5vh;
margin-bottom: 2vh;
animation: slide-in ease 0.45s;
display: flex;
justify-content: center;
font-size: 12px;
.more {
font-size: 12px;
.skip {
margin-left: 10px;
}
}
@ -68,16 +68,26 @@
.masks {
flex-grow: 1;
width: 100%;
overflow: hidden;
overflow: auto;
align-items: center;
padding-top: 20px;
$linear: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 1),
rgba(0, 0, 0, 0)
);
-webkit-mask-image: $linear;
mask-image: $linear;
animation: slide-in ease 0.5s;
.mask-row {
margin-bottom: 10px;
display: flex;
justify-content: center;
// justify-content: center;
margin-bottom: 10px;
@for $i from 1 to 10 {
&:nth-child(#{$i * 2}) {

View File

@ -27,32 +27,8 @@ function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
}
function MaskItem(props: { mask: Mask; onClick?: () => void }) {
const domRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const changeOpacity = () => {
const dom = domRef.current;
const parent = document.getElementById(SlotID.AppBody);
if (!parent || !dom) return;
const domRect = dom.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
const intersectionArea = getIntersectionArea(domRect, parentRect);
const domArea = domRect.width * domRect.height;
const ratio = intersectionArea / domArea;
const opacity = ratio > 0.9 ? 1 : 0.4;
dom.style.opacity = opacity.toString();
};
setTimeout(changeOpacity, 30);
window.addEventListener("resize", changeOpacity);
return () => window.removeEventListener("resize", changeOpacity);
}, [domRef]);
return (
<div className={styles["mask"]} ref={domRef} onClick={props.onClick}>
<div className={styles["mask"]} onClick={props.onClick}>
<MaskAvatar mask={props.mask} />
<div className={styles["mask-name"] + " one-line"}>{props.mask.name}</div>
</div>
@ -63,32 +39,38 @@ function useMaskGroup(masks: Mask[]) {
const [groups, setGroups] = useState<Mask[][]>([]);
useEffect(() => {
const appBody = document.getElementById(SlotID.AppBody);
if (!appBody || masks.length === 0) return;
const computeGroup = () => {
const appBody = document.getElementById(SlotID.AppBody);
if (!appBody || masks.length === 0) return;
const rect = appBody.getBoundingClientRect();
const maxWidth = rect.width;
const maxHeight = rect.height * 0.6;
const maskItemWidth = 120;
const maskItemHeight = 50;
const rect = appBody.getBoundingClientRect();
const maxWidth = rect.width;
const maxHeight = rect.height * 0.6;
const maskItemWidth = 120;
const maskItemHeight = 50;
const randomMask = () => masks[Math.floor(Math.random() * masks.length)];
let maskIndex = 0;
const nextMask = () => masks[maskIndex++ % masks.length];
const randomMask = () => masks[Math.floor(Math.random() * masks.length)];
let maskIndex = 0;
const nextMask = () => masks[maskIndex++ % masks.length];
const rows = Math.ceil(maxHeight / maskItemHeight);
const cols = Math.ceil(maxWidth / maskItemWidth);
const rows = Math.ceil(maxHeight / maskItemHeight);
const cols = Math.ceil(maxWidth / maskItemWidth);
const newGroups = new Array(rows)
.fill(0)
.map((_, _i) =>
new Array(cols)
.fill(0)
.map((_, j) => (j < 1 || j > cols - 2 ? randomMask() : nextMask())),
);
const newGroups = new Array(rows)
.fill(0)
.map((_, _i) =>
new Array(cols)
.fill(0)
.map((_, j) => (j < 1 || j > cols - 2 ? randomMask() : nextMask())),
);
setGroups(newGroups);
setGroups(newGroups);
};
computeGroup();
window.addEventListener("resize", computeGroup);
return () => window.removeEventListener("resize", computeGroup);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -105,6 +87,8 @@ export function NewChat() {
const navigate = useNavigate();
const config = useAppConfig();
const maskRef = useRef<HTMLDivElement>(null);
const { state } = useLocation();
const startChat = (mask?: Mask) => {
@ -123,6 +107,13 @@ export function NewChat() {
},
});
useEffect(() => {
if (maskRef.current) {
maskRef.current.scrollLeft =
(maskRef.current.scrollWidth - maskRef.current.clientWidth) / 2;
}
}, [groups]);
return (
<div className={styles["new-chat"]}>
<div className={styles["mask-header"]}>
@ -162,24 +153,24 @@ export function NewChat() {
<div className={styles["actions"]}>
<IconButton
text={Locale.NewChat.Skip}
onClick={() => startChat()}
icon={<LightningIcon />}
type="primary"
shadow
/>
<IconButton
className={styles["more"]}
text={Locale.NewChat.More}
onClick={() => navigate(Path.Masks)}
icon={<EyeIcon />}
bordered
shadow
/>
<IconButton
text={Locale.NewChat.Skip}
onClick={() => startChat()}
icon={<LightningIcon />}
type="primary"
shadow
className={styles["skip"]}
/>
</div>
<div className={styles["masks"]}>
<div className={styles["masks"]} ref={maskRef}>
{groups.map((masks, i) => (
<div key={i} className={styles["mask-row"]}>
{masks.map((mask, index) => (

View File

@ -221,11 +221,11 @@ const en: RequiredLocaleType = {
},
NewChat: {
Return: "Return",
Skip: "Skip",
Skip: "Just Start",
Title: "Pick a Mask",
SubTitle: "Chat with the Soul behind the Mask",
More: "Find More",
NotShow: "Dont Show Again",
NotShow: "Never Show Again",
ConfirmNoShow: "Confirm to disableYou can enable it in settings later.",
},

View File

@ -73,7 +73,7 @@ export const ALL_MODELS = [
available: ENABLE_GPT4,
},
{
name: "ext-davinci-002-render-sha-mobile",
name: "text-davinci-002-render-sha-mobile",
available: true,
},
{
@ -106,13 +106,13 @@ export const ALL_MODELS = [
},
] as const;
export type ModelType = typeof ALL_MODELS[number]["name"];
export type ModelType = (typeof ALL_MODELS)[number]["name"];
export function limitNumber(
x: number,
min: number,
max: number,
defaultValue: number
defaultValue: number,
) {
if (typeof x !== "number" || isNaN(x)) {
return defaultValue;
@ -171,6 +171,6 @@ export const useAppConfig = create<ChatConfigStore>()(
return state;
},
}
)
},
),
);