yangdx commited on
Commit
9accc9d
·
1 Parent(s): a2fdb3c

fix: improve form accessibility with proper label associations

Browse files

Added missing htmlFor attributes to labels and corresponding IDs to form elements throughout the web UI to enhance accessibility. This ensures screen readers can correctly identify form controls and improves browser autofill functionality. Changes include:

- Fixed label associations in login form
- Added proper IDs to form elements in Settings component
- Replaced decorative labels with semantic headings in PropertiesView
- Added screen reader accessible labels in RetrievalTesting
- Improved checkbox accessibility in QuerySettings

lightrag_webui/src/components/graph/PropertiesView.tsx CHANGED
@@ -184,9 +184,11 @@ const PropertyRow = ({
184
  return translation === translationKey ? name : translation
185
  }
186
 
 
 
187
  return (
188
  <div className="flex items-center gap-2">
189
- <label className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</label>:
190
  <Text
191
  className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
192
  tooltipClassName="max-w-80"
@@ -213,7 +215,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
213
  return (
214
  <div className="flex flex-col gap-2">
215
  <div className="flex justify-between items-center">
216
- <label className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</label>
217
  <div className="flex gap-3">
218
  <Button
219
  size="icon"
@@ -246,7 +248,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
246
  />
247
  <PropertyRow name={t('graphPanel.propertiesView.node.degree')} value={node.degree} />
248
  </div>
249
- <label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</label>
250
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
251
  {Object.keys(node.properties)
252
  .sort()
@@ -256,9 +258,9 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
256
  </div>
257
  {node.relationships.length > 0 && (
258
  <>
259
- <label className="text-md pl-1 font-bold tracking-wide text-emerald-700">
260
  {t('graphPanel.propertiesView.node.relationships')}
261
- </label>
262
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
263
  {node.relationships.map(({ type, id, label }) => {
264
  return (
@@ -283,7 +285,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
283
  const { t } = useTranslation()
284
  return (
285
  <div className="flex flex-col gap-2">
286
- <label className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</label>
287
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
288
  <PropertyRow name={t('graphPanel.propertiesView.edge.id')} value={edge.id} />
289
  {edge.type && <PropertyRow name={t('graphPanel.propertiesView.edge.type')} value={edge.type} />}
@@ -302,7 +304,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
302
  }}
303
  />
304
  </div>
305
- <label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</label>
306
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
307
  {Object.keys(edge.properties)
308
  .sort()
 
184
  return translation === translationKey ? name : translation
185
  }
186
 
187
+ // Since Text component uses a label internally, we'll use a span here instead of a label
188
+ // to avoid nesting labels which is not recommended for accessibility
189
  return (
190
  <div className="flex items-center gap-2">
191
+ <span className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</span>:
192
  <Text
193
  className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
194
  tooltipClassName="max-w-80"
 
215
  return (
216
  <div className="flex flex-col gap-2">
217
  <div className="flex justify-between items-center">
218
+ <h3 className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</h3>
219
  <div className="flex gap-3">
220
  <Button
221
  size="icon"
 
248
  />
249
  <PropertyRow name={t('graphPanel.propertiesView.node.degree')} value={node.degree} />
250
  </div>
251
+ <h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</h3>
252
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
253
  {Object.keys(node.properties)
254
  .sort()
 
258
  </div>
259
  {node.relationships.length > 0 && (
260
  <>
261
+ <h3 className="text-md pl-1 font-bold tracking-wide text-emerald-700">
262
  {t('graphPanel.propertiesView.node.relationships')}
263
+ </h3>
264
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
265
  {node.relationships.map(({ type, id, label }) => {
266
  return (
 
285
  const { t } = useTranslation()
286
  return (
287
  <div className="flex flex-col gap-2">
288
+ <h3 className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</h3>
289
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
290
  <PropertyRow name={t('graphPanel.propertiesView.edge.id')} value={edge.id} />
291
  {edge.type && <PropertyRow name={t('graphPanel.propertiesView.edge.type')} value={edge.type} />}
 
304
  }}
305
  />
306
  </div>
307
+ <h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</h3>
308
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
309
  {Object.keys(edge.properties)
310
  .sort()
lightrag_webui/src/components/graph/Settings.tsx CHANGED
@@ -23,11 +23,14 @@ const LabeledCheckBox = ({
23
  onCheckedChange: () => void
24
  label: string
25
  }) => {
 
 
 
26
  return (
27
  <div className="flex items-center gap-2">
28
- <Checkbox checked={checked} onCheckedChange={onCheckedChange} />
29
  <label
30
- htmlFor="terms"
31
  className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
32
  >
33
  {label}
@@ -56,6 +59,8 @@ const LabeledNumberInput = ({
56
  }) => {
57
  const { t } = useTranslation();
58
  const [currentValue, setCurrentValue] = useState<number | null>(value)
 
 
59
 
60
  const onValueChange = useCallback(
61
  (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -94,13 +99,14 @@ const LabeledNumberInput = ({
94
  return (
95
  <div className="flex flex-col gap-2">
96
  <label
97
- htmlFor="terms"
98
  className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
99
  >
100
  {label}
101
  </label>
102
  <div className="flex items-center gap-1">
103
  <Input
 
104
  type="number"
105
  value={currentValue === null ? '' : currentValue}
106
  onChange={onValueChange}
@@ -295,11 +301,12 @@ export default function Settings() {
295
  />
296
 
297
  <div className="flex flex-col gap-2">
298
- <label className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
299
  {t('graphPanel.sideBar.settings.edgeSizeRange')}
300
  </label>
301
  <div className="flex items-center gap-2">
302
  <Input
 
303
  type="number"
304
  value={minEdgeSize}
305
  onChange={(e) => {
@@ -315,6 +322,7 @@ export default function Settings() {
315
  <span>-</span>
316
  <div className="flex items-center gap-1">
317
  <Input
 
318
  type="number"
319
  value={maxEdgeSize}
320
  onChange={(e) => {
 
23
  onCheckedChange: () => void
24
  label: string
25
  }) => {
26
+ // Create unique ID using the label text converted to lowercase with spaces removed
27
+ const id = `checkbox-${label.toLowerCase().replace(/\s+/g, '-')}`;
28
+
29
  return (
30
  <div className="flex items-center gap-2">
31
+ <Checkbox id={id} checked={checked} onCheckedChange={onCheckedChange} />
32
  <label
33
+ htmlFor={id}
34
  className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
35
  >
36
  {label}
 
59
  }) => {
60
  const { t } = useTranslation();
61
  const [currentValue, setCurrentValue] = useState<number | null>(value)
62
+ // Create unique ID using the label text converted to lowercase with spaces removed
63
+ const id = `input-${label.toLowerCase().replace(/\s+/g, '-')}`;
64
 
65
  const onValueChange = useCallback(
66
  (e: React.ChangeEvent<HTMLInputElement>) => {
 
99
  return (
100
  <div className="flex flex-col gap-2">
101
  <label
102
+ htmlFor={id}
103
  className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
104
  >
105
  {label}
106
  </label>
107
  <div className="flex items-center gap-1">
108
  <Input
109
+ id={id}
110
  type="number"
111
  value={currentValue === null ? '' : currentValue}
112
  onChange={onValueChange}
 
301
  />
302
 
303
  <div className="flex flex-col gap-2">
304
+ <label htmlFor="edge-size-min" className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
305
  {t('graphPanel.sideBar.settings.edgeSizeRange')}
306
  </label>
307
  <div className="flex items-center gap-2">
308
  <Input
309
+ id="edge-size-min"
310
  type="number"
311
  value={minEdgeSize}
312
  onChange={(e) => {
 
322
  <span>-</span>
323
  <div className="flex items-center gap-1">
324
  <Input
325
+ id="edge-size-max"
326
  type="number"
327
  value={maxEdgeSize}
328
  onChange={(e) => {
lightrag_webui/src/components/retrieval/QuerySettings.tsx CHANGED
@@ -93,14 +93,19 @@ export default function QuerySettings() {
93
  tooltip={t('retrievePanel.querySettings.topKTooltip')}
94
  side="left"
95
  />
96
- <NumberInput
97
- id="top_k"
98
- stepper={1}
99
- value={querySettings.top_k}
100
- onValueChange={(v) => handleChange('top_k', v)}
101
- min={1}
102
- placeholder={t('retrievePanel.querySettings.topKPlaceholder')}
103
- />
 
 
 
 
 
104
  </>
105
 
106
  {/* Max Tokens */}
@@ -112,14 +117,19 @@ export default function QuerySettings() {
112
  tooltip={t('retrievePanel.querySettings.maxTokensTextUnitTooltip')}
113
  side="left"
114
  />
115
- <NumberInput
116
- id="max_token_for_text_unit"
117
- stepper={500}
118
- value={querySettings.max_token_for_text_unit}
119
- onValueChange={(v) => handleChange('max_token_for_text_unit', v)}
120
- min={1}
121
- placeholder={t('retrievePanel.querySettings.maxTokensTextUnit')}
122
- />
 
 
 
 
 
123
  </>
124
 
125
  <>
@@ -128,14 +138,19 @@ export default function QuerySettings() {
128
  tooltip={t('retrievePanel.querySettings.maxTokensGlobalContextTooltip')}
129
  side="left"
130
  />
131
- <NumberInput
132
- id="max_token_for_global_context"
133
- stepper={500}
134
- value={querySettings.max_token_for_global_context}
135
- onValueChange={(v) => handleChange('max_token_for_global_context', v)}
136
- min={1}
137
- placeholder={t('retrievePanel.querySettings.maxTokensGlobalContext')}
138
- />
 
 
 
 
 
139
  </>
140
 
141
  <>
@@ -145,14 +160,19 @@ export default function QuerySettings() {
145
  tooltip={t('retrievePanel.querySettings.maxTokensLocalContextTooltip')}
146
  side="left"
147
  />
148
- <NumberInput
149
- id="max_token_for_local_context"
150
- stepper={500}
151
- value={querySettings.max_token_for_local_context}
152
- onValueChange={(v) => handleChange('max_token_for_local_context', v)}
153
- min={1}
154
- placeholder={t('retrievePanel.querySettings.maxTokensLocalContext')}
155
- />
 
 
 
 
 
156
  </>
157
  </>
158
 
@@ -164,16 +184,21 @@ export default function QuerySettings() {
164
  tooltip={t('retrievePanel.querySettings.historyTurnsTooltip')}
165
  side="left"
166
  />
167
- <NumberInput
168
- className="!border-input"
169
- id="history_turns"
170
- stepper={1}
171
- type="text"
172
- value={querySettings.history_turns}
173
- onValueChange={(v) => handleChange('history_turns', v)}
174
- min={0}
175
- placeholder={t('retrievePanel.querySettings.historyTurnsPlaceholder')}
176
- />
 
 
 
 
 
177
  </>
178
 
179
  {/* Keywords */}
@@ -185,19 +210,24 @@ export default function QuerySettings() {
185
  tooltip={t('retrievePanel.querySettings.hlKeywordsTooltip')}
186
  side="left"
187
  />
188
- <Input
189
- id="hl_keywords"
190
- type="text"
191
- value={querySettings.hl_keywords?.join(', ')}
192
- onChange={(e) => {
193
- const keywords = e.target.value
194
- .split(',')
195
- .map((k) => k.trim())
196
- .filter((k) => k !== '')
197
- handleChange('hl_keywords', keywords)
198
- }}
199
- placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
200
- />
 
 
 
 
 
201
  </>
202
 
203
  <>
@@ -207,32 +237,38 @@ export default function QuerySettings() {
207
  tooltip={t('retrievePanel.querySettings.llKeywordsTooltip')}
208
  side="left"
209
  />
210
- <Input
211
- id="ll_keywords"
212
- type="text"
213
- value={querySettings.ll_keywords?.join(', ')}
214
- onChange={(e) => {
215
- const keywords = e.target.value
216
- .split(',')
217
- .map((k) => k.trim())
218
- .filter((k) => k !== '')
219
- handleChange('ll_keywords', keywords)
220
- }}
221
- placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
222
- />
 
 
 
 
 
223
  </>
224
  </>
225
 
226
  {/* Toggle Options */}
227
  <>
228
  <div className="flex items-center gap-2">
229
- <Text
230
- className="ml-1"
231
- text={t('retrievePanel.querySettings.onlyNeedContext')}
232
- tooltip={t('retrievePanel.querySettings.onlyNeedContextTooltip')}
233
- side="left"
234
- />
235
- <div className="grow" />
 
236
  <Checkbox
237
  className="mr-1 cursor-pointer"
238
  id="only_need_context"
@@ -242,13 +278,14 @@ export default function QuerySettings() {
242
  </div>
243
 
244
  <div className="flex items-center gap-2">
245
- <Text
246
- className="ml-1"
247
- text={t('retrievePanel.querySettings.onlyNeedPrompt')}
248
- tooltip={t('retrievePanel.querySettings.onlyNeedPromptTooltip')}
249
- side="left"
250
- />
251
- <div className="grow" />
 
252
  <Checkbox
253
  className="mr-1 cursor-pointer"
254
  id="only_need_prompt"
@@ -258,13 +295,14 @@ export default function QuerySettings() {
258
  </div>
259
 
260
  <div className="flex items-center gap-2">
261
- <Text
262
- className="ml-1"
263
- text={t('retrievePanel.querySettings.streamResponse')}
264
- tooltip={t('retrievePanel.querySettings.streamResponseTooltip')}
265
- side="left"
266
- />
267
- <div className="grow" />
 
268
  <Checkbox
269
  className="mr-1 cursor-pointer"
270
  id="stream"
 
93
  tooltip={t('retrievePanel.querySettings.topKTooltip')}
94
  side="left"
95
  />
96
+ <div>
97
+ <label htmlFor="top_k" className="sr-only">
98
+ {t('retrievePanel.querySettings.topK')}
99
+ </label>
100
+ <NumberInput
101
+ id="top_k"
102
+ stepper={1}
103
+ value={querySettings.top_k}
104
+ onValueChange={(v) => handleChange('top_k', v)}
105
+ min={1}
106
+ placeholder={t('retrievePanel.querySettings.topKPlaceholder')}
107
+ />
108
+ </div>
109
  </>
110
 
111
  {/* Max Tokens */}
 
117
  tooltip={t('retrievePanel.querySettings.maxTokensTextUnitTooltip')}
118
  side="left"
119
  />
120
+ <div>
121
+ <label htmlFor="max_token_for_text_unit" className="sr-only">
122
+ {t('retrievePanel.querySettings.maxTokensTextUnit')}
123
+ </label>
124
+ <NumberInput
125
+ id="max_token_for_text_unit"
126
+ stepper={500}
127
+ value={querySettings.max_token_for_text_unit}
128
+ onValueChange={(v) => handleChange('max_token_for_text_unit', v)}
129
+ min={1}
130
+ placeholder={t('retrievePanel.querySettings.maxTokensTextUnit')}
131
+ />
132
+ </div>
133
  </>
134
 
135
  <>
 
138
  tooltip={t('retrievePanel.querySettings.maxTokensGlobalContextTooltip')}
139
  side="left"
140
  />
141
+ <div>
142
+ <label htmlFor="max_token_for_global_context" className="sr-only">
143
+ {t('retrievePanel.querySettings.maxTokensGlobalContext')}
144
+ </label>
145
+ <NumberInput
146
+ id="max_token_for_global_context"
147
+ stepper={500}
148
+ value={querySettings.max_token_for_global_context}
149
+ onValueChange={(v) => handleChange('max_token_for_global_context', v)}
150
+ min={1}
151
+ placeholder={t('retrievePanel.querySettings.maxTokensGlobalContext')}
152
+ />
153
+ </div>
154
  </>
155
 
156
  <>
 
160
  tooltip={t('retrievePanel.querySettings.maxTokensLocalContextTooltip')}
161
  side="left"
162
  />
163
+ <div>
164
+ <label htmlFor="max_token_for_local_context" className="sr-only">
165
+ {t('retrievePanel.querySettings.maxTokensLocalContext')}
166
+ </label>
167
+ <NumberInput
168
+ id="max_token_for_local_context"
169
+ stepper={500}
170
+ value={querySettings.max_token_for_local_context}
171
+ onValueChange={(v) => handleChange('max_token_for_local_context', v)}
172
+ min={1}
173
+ placeholder={t('retrievePanel.querySettings.maxTokensLocalContext')}
174
+ />
175
+ </div>
176
  </>
177
  </>
178
 
 
184
  tooltip={t('retrievePanel.querySettings.historyTurnsTooltip')}
185
  side="left"
186
  />
187
+ <div>
188
+ <label htmlFor="history_turns" className="sr-only">
189
+ {t('retrievePanel.querySettings.historyTurns')}
190
+ </label>
191
+ <NumberInput
192
+ className="!border-input"
193
+ id="history_turns"
194
+ stepper={1}
195
+ type="text"
196
+ value={querySettings.history_turns}
197
+ onValueChange={(v) => handleChange('history_turns', v)}
198
+ min={0}
199
+ placeholder={t('retrievePanel.querySettings.historyTurnsPlaceholder')}
200
+ />
201
+ </div>
202
  </>
203
 
204
  {/* Keywords */}
 
210
  tooltip={t('retrievePanel.querySettings.hlKeywordsTooltip')}
211
  side="left"
212
  />
213
+ <div>
214
+ <label htmlFor="hl_keywords" className="sr-only">
215
+ {t('retrievePanel.querySettings.hlKeywords')}
216
+ </label>
217
+ <Input
218
+ id="hl_keywords"
219
+ type="text"
220
+ value={querySettings.hl_keywords?.join(', ')}
221
+ onChange={(e) => {
222
+ const keywords = e.target.value
223
+ .split(',')
224
+ .map((k) => k.trim())
225
+ .filter((k) => k !== '')
226
+ handleChange('hl_keywords', keywords)
227
+ }}
228
+ placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
229
+ />
230
+ </div>
231
  </>
232
 
233
  <>
 
237
  tooltip={t('retrievePanel.querySettings.llKeywordsTooltip')}
238
  side="left"
239
  />
240
+ <div>
241
+ <label htmlFor="ll_keywords" className="sr-only">
242
+ {t('retrievePanel.querySettings.llKeywords')}
243
+ </label>
244
+ <Input
245
+ id="ll_keywords"
246
+ type="text"
247
+ value={querySettings.ll_keywords?.join(', ')}
248
+ onChange={(e) => {
249
+ const keywords = e.target.value
250
+ .split(',')
251
+ .map((k) => k.trim())
252
+ .filter((k) => k !== '')
253
+ handleChange('ll_keywords', keywords)
254
+ }}
255
+ placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
256
+ />
257
+ </div>
258
  </>
259
  </>
260
 
261
  {/* Toggle Options */}
262
  <>
263
  <div className="flex items-center gap-2">
264
+ <label htmlFor="only_need_context" className="flex-1">
265
+ <Text
266
+ className="ml-1"
267
+ text={t('retrievePanel.querySettings.onlyNeedContext')}
268
+ tooltip={t('retrievePanel.querySettings.onlyNeedContextTooltip')}
269
+ side="left"
270
+ />
271
+ </label>
272
  <Checkbox
273
  className="mr-1 cursor-pointer"
274
  id="only_need_context"
 
278
  </div>
279
 
280
  <div className="flex items-center gap-2">
281
+ <label htmlFor="only_need_prompt" className="flex-1">
282
+ <Text
283
+ className="ml-1"
284
+ text={t('retrievePanel.querySettings.onlyNeedPrompt')}
285
+ tooltip={t('retrievePanel.querySettings.onlyNeedPromptTooltip')}
286
+ side="left"
287
+ />
288
+ </label>
289
  <Checkbox
290
  className="mr-1 cursor-pointer"
291
  id="only_need_prompt"
 
295
  </div>
296
 
297
  <div className="flex items-center gap-2">
298
+ <label htmlFor="stream" className="flex-1">
299
+ <Text
300
+ className="ml-1"
301
+ text={t('retrievePanel.querySettings.streamResponse')}
302
+ tooltip={t('retrievePanel.querySettings.streamResponseTooltip')}
303
+ side="left"
304
+ />
305
+ </label>
306
  <Checkbox
307
  className="mr-1 cursor-pointer"
308
  id="stream"
lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -507,8 +507,14 @@ export default function DocumentManager() {
507
  </div>
508
  </div>
509
  <div className="flex items-center gap-2">
510
- <span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
 
 
 
 
 
511
  <Button
 
512
  variant="outline"
513
  size="sm"
514
  onClick={() => setShowFileName(!showFileName)}
 
507
  </div>
508
  </div>
509
  <div className="flex items-center gap-2">
510
+ <label
511
+ htmlFor="toggle-filename-btn"
512
+ className="text-sm text-gray-500"
513
+ >
514
+ {t('documentPanel.documentManager.fileNameLabel')}
515
+ </label>
516
  <Button
517
+ id="toggle-filename-btn"
518
  variant="outline"
519
  size="sm"
520
  onClick={() => setShowFileName(!showFileName)}
lightrag_webui/src/features/RetrievalTesting.tsx CHANGED
@@ -147,13 +147,19 @@ export default function RetrievalTesting() {
147
  <EraserIcon />
148
  {t('retrievePanel.retrieval.clear')}
149
  </Button>
150
- <Input
151
- className="flex-1"
152
- value={inputValue}
153
- onChange={(e) => setInputValue(e.target.value)}
154
- placeholder={t('retrievePanel.retrieval.placeholder')}
155
- disabled={isLoading}
156
- />
 
 
 
 
 
 
157
  <Button type="submit" variant="default" disabled={isLoading} size="sm">
158
  <SendIcon />
159
  {t('retrievePanel.retrieval.send')}
 
147
  <EraserIcon />
148
  {t('retrievePanel.retrieval.clear')}
149
  </Button>
150
+ <div className="flex-1 relative">
151
+ <label htmlFor="query-input" className="sr-only">
152
+ {t('retrievePanel.retrieval.placeholder')}
153
+ </label>
154
+ <Input
155
+ id="query-input"
156
+ className="w-full"
157
+ value={inputValue}
158
+ onChange={(e) => setInputValue(e.target.value)}
159
+ placeholder={t('retrievePanel.retrieval.placeholder')}
160
+ disabled={isLoading}
161
+ />
162
+ </div>
163
  <Button type="submit" variant="default" disabled={isLoading} size="sm">
164
  <SendIcon />
165
  {t('retrievePanel.retrieval.send')}