Frontend audit: Bug fixes and refinements (#2112)

* (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

* (chore:i18n) missin keys

* (chore:i18n) missing keys

* (feat:preferrenceSlice) autoclear invalid source from storage

* (fix:general) delete all conv close btn

* (fix:tts) play one at a time

* (fix:tts) gracefully unmount

* (feat:tts) audio LRU cache

* (feat:tts) pointer on hovered area

* (feat:tts) clean text for speach

---------

Co-authored-by: GH Action - Upstream Sync <action@github.com>
This commit is contained in:
Manish Madan
2025-10-29 05:17:26 +05:30
committed by GitHub
parent 94f70e6de5
commit 6a4cb617f9
40 changed files with 1805 additions and 490 deletions

View File

@@ -44,7 +44,7 @@ export default function AddActionModal({
>
<div>
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
New Action
{t('modals.addAction.title')}
</h2>
<div className="relative mt-6 px-3">
<Input
@@ -57,7 +57,7 @@ export default function AddActionModal({
}}
borderVariant="thin"
labelBgClassName="bg-white dark:bg-charleston-green-2"
placeholder="Action Name"
placeholder={t('modals.addAction.actionNamePlaceholder')}
required={true}
/>
<p
@@ -66,8 +66,8 @@ export default function AddActionModal({
}`}
>
{functionNameError
? 'Invalid function name format. Use only letters, numbers, underscores, and hyphens.'
: 'Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)'}
? t('modals.addAction.invalidFormat')
: t('modals.addAction.formatHelp')}
</p>
</div>
<div className="mt-3 flex flex-row-reverse gap-1 px-3">
@@ -75,7 +75,7 @@ export default function AddActionModal({
onClick={handleAddAction}
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
>
Add
{t('modals.addAction.addButton')}
</button>
<button
onClick={() => {

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Agent } from '../agents/types';
@@ -24,6 +25,7 @@ export default function AgentDetailsModal({
modalState,
setModalState,
}: AgentDetailsModalProps) {
const { t } = useTranslation();
const token = useSelector(selectToken);
const [sharedToken, setSharedToken] = useState<string | null>(
@@ -86,13 +88,13 @@ export default function AgentDetailsModal({
>
<div>
<h2 className="text-jet dark:text-bright-gray text-xl font-semibold">
Access Details
{t('modals.agentDetails.title')}
</h2>
<div className="mt-8 flex flex-col gap-6">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
Public Link
{t('modals.agentDetails.publicLink')}
</h2>
</div>
{sharedToken ? (
@@ -117,7 +119,9 @@ export default function AgentDetailsModal({
target="_blank"
rel="noopener noreferrer"
>
<span className="text-sm">Learn more</span>
<span className="text-sm">
{t('modals.agentDetails.learnMore')}
</span>
<img
src="/src/assets/external-link.svg"
alt="External link"
@@ -133,14 +137,14 @@ export default function AgentDetailsModal({
{loadingStates.publicLink ? (
<Spinner size="small" color="#976af3" />
) : (
'Generate'
t('modals.agentDetails.generate')
)}
</button>
)}
</div>
<div className="flex flex-col gap-3">
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
API Key
{t('modals.agentDetails.apiKey')}
</h2>
{apiKey ? (
<div className="flex flex-col gap-2">
@@ -162,7 +166,7 @@ export default function AgentDetailsModal({
target="_blank"
rel="noopener noreferrer"
>
Test
{t('modals.agentDetails.test')}
<img
src="/src/assets/external-link.svg"
alt="External link"
@@ -174,14 +178,14 @@ export default function AgentDetailsModal({
</div>
) : (
<button className="border-purple-30 text-purple-30 hover:bg-purple-30 w-28 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white">
Generate
{t('modals.agentDetails.generate')}
</button>
)}
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
Webhook URL
{t('modals.agentDetails.webhookUrl')}
</h2>
</div>
{webhookUrl ? (
@@ -202,7 +206,9 @@ export default function AgentDetailsModal({
target="_blank"
rel="noopener noreferrer"
>
<span className="text-sm">Learn more</span>
<span className="text-sm">
{t('modals.agentDetails.learnMore')}
</span>
<img
src="/src/assets/external-link.svg"
alt="External link"
@@ -218,7 +224,7 @@ export default function AgentDetailsModal({
{loadingStates.webhook ? (
<Spinner size="small" color="#976af3" />
) : (
'Generate'
t('modals.agentDetails.generate')
)}
</button>
)}

View File

@@ -66,7 +66,7 @@ export default function ConfigToolModal({
value={customName}
onChange={(e) => setCustomName(e.target.value)}
borderVariant="thin"
placeholder="Enter custom name (optional)"
placeholder={t('modals.configTool.customNamePlaceholder')}
labelBgClassName="bg-white dark:bg-charleston-green-2"
/>
</div>

View File

@@ -1,155 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Dropdown from '../components/Dropdown';
import Input from '../components/Input';
import { CreateAPIKeyModalProps, Doc } from '../models/misc';
import { selectSourceDocs, selectToken } from '../preferences/preferenceSlice';
import WrapperModal from './WrapperModal';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
'huggingface_sentence-transformers/all-mpnet-base-v2';
export default function CreateAPIKeyModal({
close,
createAPIKey,
}: CreateAPIKeyModalProps) {
const { t } = useTranslation();
const token = useSelector(selectToken);
const docs = useSelector(selectSourceDocs);
const [APIKeyName, setAPIKeyName] = React.useState<string>('');
const [sourcePath, setSourcePath] = React.useState<{
name: string;
id: string;
type: string;
} | null>(null);
const [prompt, setPrompt] = React.useState<{
name: string;
id: string;
type: string;
} | null>(null);
const [activePrompts, setActivePrompts] = React.useState<
{ name: string; id: string; type: string }[]
>([]);
const [chunk, setChunk] = React.useState<string>('2');
const chunkOptions = ['0', '2', '4', '6', '8', '10'];
const extractDocPaths = () =>
docs
? docs
.filter((doc) => doc.model === embeddingsName)
.map((doc: Doc) => {
if ('id' in doc) {
return {
name: doc.name,
id: doc.id as string,
type: 'local',
};
}
return {
name: doc.name,
id: doc.id ?? 'default',
type: doc.type ?? 'default',
};
})
: [];
React.useEffect(() => {
const handleFetchPrompts = async () => {
try {
const response = await userService.getPrompts(token);
if (!response.ok) {
throw new Error('Failed to fetch prompts');
}
const promptsData = await response.json();
setActivePrompts(promptsData);
} catch (error) {
console.error(error);
}
};
handleFetchPrompts();
}, []);
return (
<WrapperModal close={close} className="p-4">
<div className="mb-6">
<span className="text-jet dark:text-bright-gray text-xl">
{t('modals.createAPIKey.label')}
</span>
</div>
<div className="relative mt-5 mb-4">
<Input
type="text"
className="rounded-md"
value={APIKeyName}
placeholder={t('modals.createAPIKey.apiKeyName')}
onChange={(e) => setAPIKeyName(e.target.value)}
borderVariant="thin"
labelBgClassName="bg-white dark:bg-charleston-green-2"
></Input>
</div>
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath ? sourcePath.name : null}
onSelect={(selection: { name: string; id: string; type: string }) => {
setSourcePath(selection);
}}
options={extractDocPaths()}
size="w-full"
rounded="xl"
border="border"
/>
</div>
<div className="my-4">
<Dropdown
options={activePrompts}
selectedValue={prompt ? prompt.name : null}
placeholder={t('modals.createAPIKey.prompt')}
onSelect={(value: { name: string; id: string; type: string }) =>
setPrompt(value)
}
size="w-full"
border="border"
/>
</div>
<div className="my-4">
<p className="text-jet dark:text-bright-gray mb-2 ml-2 font-semibold">
{t('modals.createAPIKey.chunks')}
</p>
<Dropdown
options={chunkOptions}
selectedValue={chunk}
onSelect={(value: string) => setChunk(value)}
size="w-full"
border="border"
/>
</div>
<button
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
onClick={() => {
if (sourcePath && prompt) {
const payload: any = {
name: APIKeyName,
prompt_id: prompt.id,
chunks: chunk,
};
if (sourcePath.type === 'default') {
payload.retriever = sourcePath.id;
}
if (sourcePath.type === 'local') {
payload.source = sourcePath.id;
}
createAPIKey(payload);
}
}}
className="bg-purple-30 hover:bg-violets-are-blue float-right mt-4 rounded-full px-5 py-2 text-sm text-white disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>
</WrapperModal>
);
}

View File

@@ -38,7 +38,7 @@ export default function DeleteConvModal({
<ConfirmationModal
message={t('modals.deleteConv.confirm')}
modalState={modalState}
setModalState={setModalState}
setModalState={(state) => dispatch(setModalState(state))}
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={handleSubmit}
handleCancel={handleCancel}

View File

@@ -18,14 +18,6 @@ interface MCPServerModalProps {
onServerSaved: () => void;
}
const authTypes = [
{ label: 'No Authentication', value: 'none' },
{ label: 'API Key', value: 'api_key' },
{ label: 'Bearer Token', value: 'bearer' },
{ label: 'OAuth', value: 'oauth' },
// { label: 'Basic Authentication', value: 'basic' },
];
export default function MCPServerModal({
modalState,
setModalState,
@@ -36,8 +28,16 @@ export default function MCPServerModal({
const token = useSelector(selectToken);
const modalRef = useRef<HTMLDivElement>(null);
const authTypes = [
{ label: t('settings.tools.mcp.authTypes.none'), value: 'none' },
{ label: t('settings.tools.mcp.authTypes.apiKey'), value: 'api_key' },
{ label: t('settings.tools.mcp.authTypes.bearer'), value: 'bearer' },
{ label: t('settings.tools.mcp.authTypes.oauth'), value: 'oauth' },
// { label: t('settings.tools.mcp.authTypes.basic'), value: 'basic' },
];
const [formData, setFormData] = useState({
name: server?.displayName || 'My MCP Server',
name: server?.displayName || t('settings.tools.mcp.defaultServerName'),
server_url: server?.server_url || '',
auth_type: server?.auth_type || 'none',
api_key: '',
@@ -72,7 +72,7 @@ export default function MCPServerModal({
const resetForm = () => {
setFormData({
name: 'My MCP Server',
name: t('settings.tools.mcp.defaultServerName'),
server_url: '',
auth_type: 'none',
api_key: '',
@@ -133,7 +133,7 @@ export default function MCPServerModal({
typeof timeoutValue === 'number' &&
(timeoutValue < 1 || timeoutValue > 300)
)
newErrors.timeout = 'Timeout must be between 1 and 300 seconds';
newErrors.timeout = t('settings.tools.mcp.errors.timeoutRange');
if (authFieldChecks[formData.auth_type])
authFieldChecks[formData.auth_type]();

View File

@@ -1,51 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SaveAPIKeyModalProps } from '../models/misc';
import WrapperModal from './WrapperModal';
export default function SaveAPIKeyModal({
apiKey,
close,
}: SaveAPIKeyModalProps) {
const { t } = useTranslation();
const [isCopied, setIsCopied] = React.useState(false);
const handleCopyKey = () => {
navigator.clipboard.writeText(apiKey);
setIsCopied(true);
};
return (
<WrapperModal close={close}>
<h1 className="text-jet dark:text-bright-gray my-0 text-xl font-medium">
{t('modals.saveKey.note')}
</h1>
<h3 className="text-outer-space dark:text-silver text-sm font-normal">
{t('modals.saveKey.disclaimer')}
</h3>
<div className="flex justify-between py-2">
<div>
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
API Key
</h2>
<span className="text-jet dark:text-bright-gray text-sm leading-7 font-normal">
{apiKey}
</span>
</div>
<button
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue my-1 h-10 w-20 rounded-full border border-solid p-2 text-sm hover:text-white"
onClick={handleCopyKey}
>
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
</button>
</div>
<button
onClick={close}
className="bg-philippine-yellow rounded-full px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
>
{t('modals.saveKey.confirm')}
</button>
</WrapperModal>
);
}