Skip to content

Commit ee2cb93

Browse files
authored
refactor: improve state management and accessibility in components (#123)
- Updated `LoadingIndicator` to use `useReducer` for better state handling. - Refactored `OnboardingGuide` to utilize `useReducer` for image loading state. - Enhanced accessibility by adding `role` and `tabIndex` attributes to various interactive elements across components. - Simplified state updates in `ChatHistoryModal` and `LoginWithOverleaf` by consolidating state management. - Improved performance in `CodeBlock` by using `useMemo` for highlighted code. - General code cleanup and consistency improvements across multiple components.
1 parent ea701a5 commit ee2cb93

33 files changed

Lines changed: 286 additions & 113 deletions

webapp/_webapp/src/components/code-block.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,15 @@ import "highlight.js/styles/default.min.css";
33
import latex from "highlight.js/lib/languages/latex";
44
hljs.registerLanguage("latex", latex);
55

6-
import { useState, useEffect } from "react";
6+
import { useMemo } from "react";
77

88
type CodeBlockProps = {
99
code: string;
1010
className?: string;
1111
};
1212

1313
export const CodeBlock = ({ code, className }: CodeBlockProps) => {
14-
const [highlightedCode, setHighlightedCode] = useState(code);
15-
16-
useEffect(() => {
17-
setHighlightedCode(hljs.highlight(code, { language: "latex" }).value);
18-
}, [code]);
14+
const highlightedCode = useMemo(() => hljs.highlight(code, { language: "latex" }).value, [code]);
1915

2016
return (
2117
<pre

webapp/_webapp/src/components/loading-indicator.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from "react";
1+
import { useReducer, useEffect } from "react";
22

33
// ============================================================================
44
// Types
@@ -49,11 +49,37 @@ const PHASE_STYLES = {
4949
// Component
5050
// ============================================================================
5151

52+
type IndicatorState = {
53+
progress: number;
54+
phase: Phase;
55+
isTimeout: boolean;
56+
};
57+
58+
type IndicatorAction =
59+
| { type: "SET_PROGRESS"; progress: number }
60+
| { type: "ADVANCE_PHASE"; nextPhase: Phase }
61+
| { type: "SET_TIMEOUT" };
62+
63+
function indicatorReducer(state: IndicatorState, action: IndicatorAction): IndicatorState {
64+
switch (action.type) {
65+
case "SET_PROGRESS":
66+
return { ...state, progress: action.progress };
67+
case "ADVANCE_PHASE":
68+
return { ...state, phase: action.nextPhase, progress: 0 };
69+
case "SET_TIMEOUT":
70+
return { ...state, isTimeout: true };
71+
default:
72+
return state;
73+
}
74+
}
75+
5276
export const LoadingIndicator = ({ text = "Thinking", estimatedSeconds = 0, errorMessage }: LoadingIndicatorProps) => {
5377
// State
54-
const [progress, setProgress] = useState(0);
55-
const [phase, setPhase] = useState<Phase>("green");
56-
const [isTimeout, setIsTimeout] = useState(false);
78+
const [{ progress, phase, isTimeout }, dispatch] = useReducer(indicatorReducer, {
79+
progress: 0,
80+
phase: "green",
81+
isTimeout: false,
82+
});
5783

5884
// Handle progress animation
5985
useEffect(() => {
@@ -103,18 +129,18 @@ export const LoadingIndicator = ({ text = "Thinking", estimatedSeconds = 0, erro
103129
// we spend 100% of estimatedDuration in green,
104130
// 50% in orange, and 50% in red before warning.
105131
if (phase === "green") {
106-
setPhase("orange");
132+
dispatch({ type: "ADVANCE_PHASE", nextPhase: "orange" });
107133
currentProgress = 0;
108134
} else if (phase === "orange") {
109-
setPhase("red");
135+
dispatch({ type: "ADVANCE_PHASE", nextPhase: "red" });
110136
currentProgress = 0;
111137
} else if (phase === "red") {
112-
setIsTimeout(true);
138+
dispatch({ type: "SET_TIMEOUT" });
113139
return;
114140
}
115141
}
116142

117-
setProgress(currentProgress);
143+
dispatch({ type: "SET_PROGRESS", progress: currentProgress });
118144

119145
if (!isTimeout) {
120146
animationFrameId = requestAnimationFrame(updateProgress);

webapp/_webapp/src/components/message-entry-container/assistant.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ export const AssistantMessageContainer = ({
132132
)}
133133

134134
{/* PaperDebugger blocks */}
135-
{parsedMessage.paperDebuggerContent.map((content, index) => (
136-
<TextPatches key={index} attachment={prevAttachment}>
135+
{parsedMessage.paperDebuggerContent.map((content) => (
136+
<TextPatches key={content} attachment={prevAttachment}>
137137
{content}
138138
</TextPatches>
139139
))}
@@ -147,7 +147,7 @@ export const AssistantMessageContainer = ({
147147
{((parsedMessage.regularContent?.length || 0) > 0 || parsedMessage.paperDebuggerContent.length > 0) && (
148148
<div className="actions rnd-cancel noselect">
149149
<Tooltip content="Copy" placement="bottom" size="sm" delay={1000}>
150-
<span onClick={handleCopy} tabIndex={0} role="button" aria-label="Copy message">
150+
<span onClick={handleCopy} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { handleCopy(); } }} tabIndex={0} role="button" aria-label="Copy message">
151151
<Icon icon={copySuccess ? "tabler:copy-check" : "tabler:copy"} className="icon" />
152152
</span>
153153
</Tooltip>

webapp/_webapp/src/components/message-entry-container/tools/general.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export const GeneralToolCard = ({
9595
// When there is a message, show the compact card with collapsible content
9696
return (
9797
<div className={cn("tool-card noselect compact", { animated: animated })}>
98-
<div className="flex items-center gap-1 cursor-pointer" onClick={toggleCollapse}>
98+
<div className="flex items-center gap-1 cursor-pointer" role="button" tabIndex={0} onClick={toggleCollapse} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { toggleCollapse(); } }}>
9999
<button
100100
className="text-gray-400 hover:text-gray-600 transition-colors duration-200 rounded flex"
101101
aria-label={isCollapsed ? "Expand" : "Collapse"}

webapp/_webapp/src/components/message-entry-container/tools/paper-score-comment/filter-controls.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export const FilterControls = ({
3838
<div>
3939
<div
4040
className="!flex !items-center !justify-between !mb-1 !px-2 cursor-pointer"
41+
role="button"
42+
tabIndex={0}
4143
onClick={() => setIsSuggestionsExpanded(!isSuggestionsExpanded)}
44+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsSuggestionsExpanded(!isSuggestionsExpanded); } }}
4245
>
4346
<button className="!flex !items-center !gap-2 !text-sm !font-semibold !text-gray-800 hover:!text-gray-600 !transition-colors truncate">
4447
<Icon

webapp/_webapp/src/components/message-entry-container/tools/tools.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PaperScoreCard } from "./paper-score";
2-
import { PaperScoreCommentCard } from "./paper-score-comment/index";
2+
import { PaperScoreCommentCard } from "./paper-score-comment";
33
import { GreetingCard } from "./greeting";
44
import { ErrorToolCard } from "./error";
55
import { AlwaysExceptionCard } from "./always-exception";

webapp/_webapp/src/components/message-entry-container/tools/xtramcp/generate-citations.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export const GenerateCitationsCard = ({ functionName, message, preparing, animat
4040
{/* Header with Error label and arrow button */}
4141
<div
4242
className="flex items-center justify-between cursor-pointer"
43+
role="button"
44+
tabIndex={0}
4345
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
46+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
4447
>
4548
<h3 className="tool-card-title">{functionName}</h3>
4649
<div className="flex items-center gap-2">
@@ -68,7 +71,10 @@ export const GenerateCitationsCard = ({ functionName, message, preparing, animat
6871
{/* Header with arrow button */}
6972
<div
7073
className="flex items-center cursor-pointer gap-1"
74+
role="button"
75+
tabIndex={0}
7176
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
77+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
7278
>
7379
<button
7480
className="text-gray-400 hover:text-gray-600 transition-colors duration-200 rounded flex"

webapp/_webapp/src/components/message-entry-container/tools/xtramcp/online-search-papers.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export const OnlineSearchPapersCard = ({ functionName, message, preparing, anima
4040
{/* Header with Error label and arrow button */}
4141
<div
4242
className="flex items-center justify-between cursor-pointer"
43+
role="button"
44+
tabIndex={0}
4345
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
46+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
4447
>
4548
<h3 className="tool-card-title">{functionName}</h3>
4649
<div className="flex items-center gap-2">
@@ -72,7 +75,10 @@ export const OnlineSearchPapersCard = ({ functionName, message, preparing, anima
7275
{/* Header with arrow button */}
7376
<div
7477
className="flex items-center justify-between cursor-pointer"
78+
role="button"
79+
tabIndex={0}
7580
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
81+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
7682
>
7783
<h3 className="tool-card-title">{functionName}</h3>
7884
<CollapseArrowButton

webapp/_webapp/src/components/message-entry-container/tools/xtramcp/review-paper.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import MarkdownComponent from "../../../markdown";
44
import { useState } from "react";
55
import { XtraMcpToolCardProps, parseXtraMcpToolResult, CollapseArrowButton, CollapseWrapper } from "./utils/common";
66

7-
// Helper function to render severity levels with strikethrough
8-
const renderSeverityLevels = (threshold: string) => {
7+
// Component to render severity levels with strikethrough
8+
const SeverityLevels = ({ threshold }: { threshold: string }) => {
99
const levels = ["nit", "minor", "major", "blocker"];
1010
const thresholdIndex = levels.indexOf(threshold.toLowerCase());
1111

@@ -30,7 +30,7 @@ const renderSeverityLevels = (threshold: string) => {
3030
);
3131
};
3232

33-
const renderSections = (sections: Array<string>) => {
33+
const Sections = ({ sections }: { sections: Array<string> }) => {
3434
return (
3535
<span>
3636
{sections.map((section, index) => (
@@ -81,7 +81,10 @@ export const ReviewPaperCard = ({ functionName, message, preparing, animated }:
8181
{/* Header with Error label and arrow button */}
8282
<div
8383
className="flex items-center justify-between cursor-pointer"
84+
role="button"
85+
tabIndex={0}
8486
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
87+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
8588
>
8689
<h3 className="tool-card-title">{functionName}</h3>
8790
<div className="flex items-center gap-2">
@@ -113,7 +116,10 @@ export const ReviewPaperCard = ({ functionName, message, preparing, animated }:
113116
{/* Header with arrow button */}
114117
<div
115118
className="flex items-center justify-between cursor-pointer"
119+
role="button"
120+
tabIndex={0}
116121
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
122+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
117123
>
118124
<h3 className="tool-card-title">{functionName}</h3>
119125
<CollapseArrowButton
@@ -144,13 +150,13 @@ export const ReviewPaperCard = ({ functionName, message, preparing, animated }:
144150
{result.metadata.severity_threshold && (
145151
<div className="mb-2">
146152
<span className="font-medium">Filtered:</span>{" "}
147-
{renderSeverityLevels(result.metadata.severity_threshold)}
153+
<SeverityLevels threshold={result.metadata.severity_threshold} />
148154
</div>
149155
)}
150156
{result.metadata.sections_reviewed && (
151157
<div>
152158
<span className="font-medium">Sections reviewed:</span>{" "}
153-
{renderSections(result.metadata.sections_reviewed)}
159+
<Sections sections={result.metadata.sections_reviewed} />
154160
</div>
155161
)}
156162
</div>

webapp/_webapp/src/components/message-entry-container/tools/xtramcp/search-relevant-papers.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ export const SearchRelevantPapersCard = ({ functionName, message, preparing, ani
4848
{/* Header with Error label and arrow button */}
4949
<div
5050
className="flex items-center justify-between cursor-pointer"
51+
role="button"
52+
tabIndex={0}
5153
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
54+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
5255
>
5356
<h3 className="tool-card-title">{functionName}</h3>
5457
<div className="flex items-center gap-2">
@@ -80,7 +83,10 @@ export const SearchRelevantPapersCard = ({ functionName, message, preparing, ani
8083
{/* Header with arrow button */}
8184
<div
8285
className="flex items-center justify-between cursor-pointer"
86+
role="button"
87+
tabIndex={0}
8388
onClick={() => setIsMetadataCollapsed(!isMetadataCollapsed)}
89+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setIsMetadataCollapsed(!isMetadataCollapsed); } }}
8490
>
8591
<h3 className="tool-card-title">{functionName}</h3>
8692
<CollapseArrowButton

0 commit comments

Comments
 (0)