Frontend audit: refinements (#2083)

* (fix:attachements) sep id for redux ops

* (fix:ui) popups, toast, share modal

* (feat:agentsPreview) stable preview, ui fixes

* (fix:ui) light theme icon, sleek scroll

---------

Co-authored-by: GH Action - Upstream Sync <action@github.com>
This commit is contained in:
Manish Madan
2025-10-22 14:42:05 +05:30
committed by GitHub
parent 5aa4ec1b9f
commit c4e8daf50e
21 changed files with 121 additions and 86 deletions

View File

@@ -109,18 +109,18 @@ export default function AgentPreview() {
} else setLastQueryReturnedErr(false);
}, [queries]);
return (
<div>
<div className="dark:bg-raisin-black flex h-full flex-col items-center justify-between gap-2 overflow-y-hidden">
<div className="h-[512px] w-full overflow-y-auto">
<ConversationMessages
handleQuestion={handleQuestion}
handleQuestionSubmission={handleQuestionSubmission}
queries={queries}
status={status}
showHeroOnEmpty={false}
/>
</div>
<div className="flex w-[95%] max-w-[1500px] flex-col items-center gap-4 pb-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
<div className="relative h-full w-full">
<div className="scrollbar-thin absolute inset-0 bottom-[180px] overflow-hidden px-4 pt-4 [&>div>div]:!w-full [&>div>div]:!max-w-none">
<ConversationMessages
handleQuestion={handleQuestion}
handleQuestionSubmission={handleQuestionSubmission}
queries={queries}
status={status}
showHeroOnEmpty={false}
/>
</div>
<div className="absolute right-0 bottom-0 left-0 flex w-full flex-col gap-4 pb-2">
<div className="w-full px-4">
<MessageInput
onSubmit={(text) => handleQuestionSubmission(text)}
loading={status === 'loading'}
@@ -128,11 +128,11 @@ export default function AgentPreview() {
showToolButton={selectedAgent ? false : true}
autoFocus={false}
/>
<p className="text-gray-4000 dark:text-sonic-silver w-full self-center bg-transparent pt-2 text-center text-xs md:inline">
This is a preview of the agent. You can publish it to start using it
in conversations.
</p>
</div>
<p className="text-gray-4000 dark:text-sonic-silver w-full bg-transparent text-center text-xs md:inline">
This is a preview of the agent. You can publish it to start using it
in conversations.
</p>
</div>
</div>
);

View File

@@ -534,7 +534,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
setHasChanges(isChanged);
}, [agent, dispatch, effectiveMode, imageFile, jsonSchemaText]);
return (
<div className="p-4 md:p-12">
<div className="flex flex-col px-4 pt-4 pb-2 max-[1179px]:min-h-[100dvh] min-[1180px]:h-[100dvh] md:px-12 md:pt-12 md:pb-3">
<div className="flex items-center gap-3 px-4">
<button
className="rounded-full border p-3 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
@@ -615,9 +615,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
</button>
</div>
</div>
<div className="mt-5 flex w-full grid-cols-5 flex-col gap-10 min-[1180px]:grid min-[1180px]:gap-5">
<div className="col-span-2 flex flex-col gap-5">
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="mt-3 flex w-full flex-1 grid-cols-5 flex-col gap-10 rounded-[30px] bg-[#F6F6F6] p-5 max-[1179px]:overflow-visible min-[1180px]:grid min-[1180px]:gap-5 min-[1180px]:overflow-hidden dark:bg-[#383838]">
<div className="scrollbar-thin col-span-2 flex flex-col gap-5 max-[1179px]:overflow-visible min-[1180px]:max-h-full min-[1180px]:overflow-y-auto min-[1180px]:pr-3">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<h2 className="text-lg font-semibold">Meta</h2>
<input
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
@@ -650,7 +650,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
/>
</div>
</div>
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<h2 className="text-lg font-semibold">Source</h2>
<div className="mt-3">
<div className="flex flex-wrap items-center gap-1">
@@ -744,7 +744,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
</div>
</div>
</div>
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<div className="flex flex-wrap items-end gap-1">
<div className="min-w-20 grow basis-full sm:basis-0">
<Prompts
@@ -781,7 +781,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
</button>
</div>
</div>
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<h2 className="text-lg font-semibold">Tools</h2>
<div className="mt-3 flex flex-wrap items-center gap-1">
<button
@@ -823,7 +823,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
/>
</div>
</div>
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<h2 className="text-lg font-semibold">Agent type</h2>
<div className="mt-3">
<Dropdown
@@ -848,7 +848,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
/>
</div>
</div>
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
<button
onClick={() =>
setIsAdvancedSectionExpanded(!isAdvancedSectionExpanded)
@@ -1032,9 +1032,11 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
)}
</div>
</div>
<div className="col-span-3 flex flex-col gap-3 rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
<div className="col-span-3 flex flex-col gap-2 max-[1179px]:h-auto max-[1179px]:px-0 max-[1179px]:py-0 min-[1180px]:h-full min-[1180px]:py-2 dark:text-[#E0E0E0]">
<h2 className="text-lg font-semibold">Preview</h2>
<AgentPreviewArea />
<div className="flex-1 max-[1179px]:overflow-visible min-[1180px]:min-h-0 min-[1180px]:overflow-hidden">
<AgentPreviewArea />
</div>
</div>
</div>
<ConfirmationModal
@@ -1071,9 +1073,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
function AgentPreviewArea() {
const selectedAgent = useSelector(selectSelectedAgent);
return (
<div className="dark:bg-raisin-black h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white max-[1180px]:h-192 dark:border-[#7E7E7E]">
<div className="dark:bg-raisin-black w-full rounded-[30px] border border-[#F6F6F6] bg-white max-[1179px]:h-[600px] min-[1180px]:h-full dark:border-[#7E7E7E]">
{selectedAgent?.status === 'published' ? (
<div className="flex h-full w-full flex-col justify-end overflow-auto rounded-[30px]">
<div className="flex h-full w-full flex-col overflow-hidden rounded-[30px]">
<AgentPreview />
</div>
) : (

View File

@@ -177,13 +177,15 @@ export default function SharedAgent() {
/>
</div>
<div className="flex w-[95%] max-w-[1500px] flex-col items-center pb-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
<MessageInput
onSubmit={(text) => handleQuestionSubmission(text)}
loading={status === 'loading'}
showSourceButton={sharedAgent ? false : true}
showToolButton={sharedAgent ? false : true}
autoFocus={false}
/>
<div className="w-full px-2">
<MessageInput
onSubmit={(text) => handleQuestionSubmission(text)}
loading={status === 'loading'}
showSourceButton={sharedAgent ? false : true}
showToolButton={sharedAgent ? false : true}
autoFocus={false}
/>
</div>
<p className="text-gray-4000 dark:text-sonic-silver hidden w-screen self-center bg-transparent py-2 text-center text-xs md:inline md:w-full">
{t('tagline')}
</p>

View File

@@ -1,5 +1,5 @@
<svg width="113" height="124" viewBox="0 0 113 124" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="55.5" cy="71" r="53" fill="#F1F1F1" fill-opacity="0.5"/>
<circle cx="55.5" cy="71" r="53" fill="#E8E3F3" fill-opacity="0.6"/>
<rect x="-0.599797" y="0.654564" width="43.9445" height="61.5222" rx="4.39444" transform="matrix(-0.999048 0.0436194 0.0436194 0.999048 68.9873 43.3176)" fill="#EEEEEE" stroke="#999999" stroke-width="1.25556"/>
<rect x="0.704349" y="-0.540466" width="46.4556" height="64.0333" rx="5.65" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 96.3673 40.893)" fill="#FAFAFA" stroke="#999999" stroke-width="1.25556"/>
<path d="M94.3796 45.7849C94.7417 43.0349 92.8059 40.5122 90.0559 40.1501L55.2011 35.5614C52.4511 35.1994 49.9284 37.1352 49.5663 39.8851L48.3372 49.2212L93.1505 55.121L94.3796 45.7849Z" fill="#EEEEEE"/>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -91,8 +91,10 @@ export default function MessageInput({
const apiHost = import.meta.env.VITE_API_HOST;
const xhr = new XMLHttpRequest();
const uniqueId = crypto.randomUUID();
const newAttachment = {
id: uniqueId,
fileName: file.name,
progress: 0,
status: 'uploading' as const,
@@ -106,7 +108,7 @@ export default function MessageInput({
const progress = Math.round((event.loaded / event.total) * 100);
dispatch(
updateAttachment({
taskId: newAttachment.taskId,
id: uniqueId,
updates: { progress },
}),
);
@@ -119,7 +121,7 @@ export default function MessageInput({
if (response.task_id) {
dispatch(
updateAttachment({
taskId: newAttachment.taskId,
id: uniqueId,
updates: {
taskId: response.task_id,
status: 'processing',
@@ -131,7 +133,7 @@ export default function MessageInput({
} else {
dispatch(
updateAttachment({
taskId: newAttachment.taskId,
id: uniqueId,
updates: { status: 'failed' },
}),
);
@@ -141,7 +143,7 @@ export default function MessageInput({
xhr.onerror = () => {
dispatch(
updateAttachment({
taskId: newAttachment.taskId,
id: uniqueId,
updates: { status: 'failed' },
}),
);
@@ -167,7 +169,7 @@ export default function MessageInput({
if (data.status === 'SUCCESS') {
dispatch(
updateAttachment({
taskId: attachment.taskId!,
id: attachment.id,
updates: {
status: 'completed',
progress: 100,
@@ -179,14 +181,14 @@ export default function MessageInput({
} else if (data.status === 'FAILURE') {
dispatch(
updateAttachment({
taskId: attachment.taskId!,
id: attachment.id,
updates: { status: 'failed' },
}),
);
} else if (data.status === 'PROGRESS' && data.result?.current) {
dispatch(
updateAttachment({
taskId: attachment.taskId!,
id: attachment.id,
updates: { progress: data.result.current },
}),
);
@@ -195,7 +197,7 @@ export default function MessageInput({
.catch(() => {
dispatch(
updateAttachment({
taskId: attachment.taskId!,
id: attachment.id,
updates: { status: 'failed' },
}),
);
@@ -260,12 +262,12 @@ export default function MessageInput({
};
return (
<div className="mx-2 flex w-full flex-col">
<div className="flex w-full flex-col">
<div className="border-dark-gray bg-lotion dark:border-grey relative flex w-full flex-col rounded-[23px] border dark:bg-transparent">
<div className="flex flex-wrap gap-1.5 px-2 py-2 sm:gap-2 sm:px-3">
{attachments.map((attachment, index) => (
{attachments.map((attachment) => (
<div
key={index}
key={attachment.id}
className={`group dark:text-bright-gray relative flex items-center rounded-xl bg-[#EFF3F4] px-2 py-1 text-[12px] text-[#5D5D5D] sm:px-3 sm:py-1.5 sm:text-[14px] dark:bg-[#393B3D] ${
attachment.status !== 'completed' ? 'opacity-70' : 'opacity-100'
}`}
@@ -327,11 +329,7 @@ export default function MessageInput({
<button
className="ml-1.5 flex items-center justify-center rounded-full p-1"
onClick={() => {
if (attachment.id) {
dispatch(removeAttachment(attachment.id));
} else if (attachment.taskId) {
dispatch(removeAttachment(attachment.taskId));
}
dispatch(removeAttachment(attachment.id));
}}
aria-label={t('conversation.attachments.remove')}
>

View File

@@ -33,7 +33,7 @@ export default function Sidebar({
return (
<div ref={sidebarRef} className="h-vh relative">
<div
className={`dark:bg-chinese-black fixed top-0 right-0 z-50 h-full w-72 transform bg-white shadow-xl transition-all duration-300 sm:w-96 ${
className={`dark:bg-chinese-black fixed top-0 right-0 z-50 h-full w-64 transform bg-white shadow-xl transition-all duration-300 sm:w-80 ${
isOpen ? 'translate-x-[10px]' : 'translate-x-full'
} border-l border-[#9ca3af]/10`}
>

View File

@@ -1,4 +1,5 @@
import React, { useRef, useEffect, useState, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Doc } from '../models/misc';
@@ -107,7 +108,7 @@ export default function SourcesPopup({
onClose();
};
return (
const popupContent = (
<div
ref={popupRef}
className="bg-lotion dark:bg-charleston-green-2 fixed z-50 flex flex-col rounded-xl shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
@@ -218,7 +219,7 @@ export default function SourcesPopup({
</>
) : (
<div className="dark:text-bright-gray p-4 text-center text-gray-500 dark:text-[14px]">
{t('noSourcesAvailable')}
{t('conversation.sources.noSourcesAvailable')}
</div>
)}
</div>
@@ -245,4 +246,6 @@ export default function SourcesPopup({
</div>
</div>
);
return createPortal(popupContent, document.body);
}

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { selectToken } from '../preferences/preferenceSlice';
@@ -133,10 +134,10 @@ export default function ToolsPopup({
tool.displayName.toLowerCase().includes(searchTerm.toLowerCase()),
);
return (
const popupContent = (
<div
ref={popupRef}
className="border-light-silver bg-lotion dark:border-dim-gray dark:bg-charleston-green-2 fixed z-9999 rounded-lg border shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
className="border-light-silver bg-lotion dark:border-dim-gray dark:bg-charleston-green-2 fixed z-50 rounded-lg border shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
style={{
top: popupPosition.showAbove ? popupPosition.top : undefined,
bottom: popupPosition.showAbove
@@ -242,4 +243,6 @@ export default function ToolsPopup({
</div>
</div>
);
return createPortal(popupContent, document.body);
}

View File

@@ -44,7 +44,10 @@ export default function UploadToast() {
};
return (
<div className="fixed right-4 bottom-4 z-50 flex max-w-md flex-col gap-2">
<div
className="fixed right-4 bottom-4 z-50 flex max-w-md flex-col gap-2"
onMouseDown={(e) => e.stopPropagation()}
>
{uploadTasks
.filter((task) => !task.dismissed)
.map((task) => {

View File

@@ -224,7 +224,7 @@ export default function Conversation() {
<div className="bg-opacity-0 z-3 flex h-auto w-full max-w-[1300px] flex-col items-end self-center rounded-2xl py-1 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
<div
{...getRootProps()}
className="flex w-full items-center rounded-[40px]"
className="flex w-full items-center rounded-[40px] px-2"
>
<label htmlFor="file-upload" className="sr-only">
{t('modals.uploadDoc.label')}

View File

@@ -635,7 +635,7 @@ function AllSources(sources: AllSourcesProps) {
<p className="text-left text-xl">{`${sources.sources.length} ${t('conversation.sources.title')}`}</p>
<div className="mx-1 mt-2 h-[0.8px] w-full rounded-full bg-[#C4C4C4]/40 lg:w-[95%]"></div>
</div>
<div className="mt-6 flex h-[90%] w-60 flex-col items-center gap-4 overflow-y-auto sm:w-80">
<div className="scrollbar-thin mt-6 flex h-[90%] w-52 flex-col gap-4 overflow-y-auto pr-3 sm:w-64">
{sources.sources.map((source, index) => {
const isExternalSource = source.link && source.link !== 'local';
return (

View File

@@ -161,14 +161,16 @@ export const SharedConversation = () => {
/>
<div className="flex w-full max-w-[1200px] flex-col items-center gap-4 pb-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
{apiKey ? (
<MessageInput
onSubmit={(text) => {
handleQuestionSubmission(text);
}}
loading={status === 'loading'}
showSourceButton={false}
showToolButton={false}
/>
<div className="w-full px-2">
<MessageInput
onSubmit={(text) => {
handleQuestionSubmission(text);
}}
loading={status === 'loading'}
showSourceButton={false}
showToolButton={false}
/>
</div>
) : (
<button
onClick={() => navigate('/')}

View File

@@ -118,18 +118,34 @@ layer(base);
background: transparent;
}
/* Light theme scrollbar */
&::-webkit-scrollbar-thumb {
background: rgba(156, 163, 175, 0.5);
background: rgba(215, 215, 215, 1);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: rgba(156, 163, 175, 0.7);
background: rgba(195, 195, 195, 1);
}
/* For Firefox */
/* Dark theme scrollbar */
.dark &::-webkit-scrollbar-thumb {
background: rgba(77, 78, 88, 1);
border-radius: 3px;
}
.dark &::-webkit-scrollbar-thumb:hover {
background: rgba(97, 98, 108, 1);
}
/* For Firefox - Light theme */
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
scrollbar-color: rgba(215, 215, 215, 1) transparent;
/* For Firefox - Dark theme */
.dark & {
scrollbar-color: rgba(77, 78, 88, 1) transparent;
}
}
@utility table-default {

View File

@@ -421,7 +421,8 @@
"title": "Sources",
"text": "Choose Your Sources",
"link": "Source link",
"view_more": "{{count}} more sources"
"view_more": "{{count}} more sources",
"noSourcesAvailable": "No sources available"
},
"attachments": {
"attach": "Attach",

View File

@@ -384,7 +384,8 @@
"title": "Fuentes",
"link": "Enlace fuente",
"view_more": "Ver {{count}} más fuentes",
"text": "Elegir tus fuentes"
"text": "Elegir tus fuentes",
"noSourcesAvailable": "No hay fuentes disponibles"
},
"attachments": {
"attach": "Adjuntar",

View File

@@ -384,7 +384,8 @@
"title": "ソース",
"text": "ソーステキスト",
"link": "ソースリンク",
"view_more": "さらに{{count}}個のソース"
"view_more": "さらに{{count}}個のソース",
"noSourcesAvailable": "利用可能なソースがありません"
},
"attachments": {
"attach": "添付",

View File

@@ -384,7 +384,8 @@
"title": "Источники",
"text": "Выберите ваши источники",
"link": "Ссылка на источник",
"view_more": "ещё {{count}} источников"
"view_more": "ещё {{count}} источников",
"noSourcesAvailable": "Нет доступных источников"
},
"attachments": {
"attach": "Прикрепить",

View File

@@ -384,7 +384,8 @@
"title": "來源",
"text": "來源文字",
"link": "來源連結",
"view_more": "查看更多 {{count}} 個來源"
"view_more": "查看更多 {{count}} 個來源",
"noSourcesAvailable": "沒有可用的來源"
},
"attachments": {
"attach": "附件",

View File

@@ -384,7 +384,8 @@
"title": "来源",
"text": "来源文本",
"link": "来源链接",
"view_more": "还有{{count}}个来源"
"view_more": "还有{{count}}个来源",
"noSourcesAvailable": "没有可用的来源"
},
"attachments": {
"attach": "附件",

View File

@@ -103,8 +103,8 @@ export const ShareConversationModal = ({
};
return (
<WrapperModal close={close}>
<div className="flex max-h-[80vh] w-[600px] max-w-[80vw] flex-col gap-2 overflow-y-auto">
<WrapperModal close={close} contentClassName="!overflow-visible">
<div className="flex w-[600px] max-w-[80vw] flex-col gap-2">
<h2 className="text-eerie-black dark:text-chinese-white text-xl font-medium">
{t('modals.shareConv.label')}
</h2>

View File

@@ -2,11 +2,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../store';
export interface Attachment {
id: string; // Unique identifier for the attachment (required for state management)
fileName: string;
progress: number;
status: 'uploading' | 'processing' | 'completed' | 'failed';
taskId: string;
id?: string;
taskId: string; // Server-assigned task ID (used for API calls)
token_count?: number;
}
@@ -47,12 +47,12 @@ export const uploadSlice = createSlice({
updateAttachment: (
state,
action: PayloadAction<{
taskId: string;
id: string;
updates: Partial<Attachment>;
}>,
) => {
const index = state.attachments.findIndex(
(att) => att.taskId === action.payload.taskId,
(att) => att.id === action.payload.id,
);
if (index !== -1) {
state.attachments[index] = {
@@ -63,7 +63,7 @@ export const uploadSlice = createSlice({
},
removeAttachment: (state, action: PayloadAction<string>) => {
state.attachments = state.attachments.filter(
(att) => att.taskId !== action.payload && att.id !== action.payload,
(att) => att.id !== action.payload,
);
},
clearAttachments: (state) => {