Merge pull request #1291 from danielaskdd/main

fix: improve form accessibility with proper label associations
This commit is contained in:
Daniel.y
2025-04-07 05:27:07 +08:00
committed by GitHub
11 changed files with 257 additions and 197 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@
<link rel="icon" type="image/svg+xml" href="logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lightrag</title>
<script type="module" crossorigin src="/webui/assets/index-DXSe8IZZ.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-QU59h9JG.css">
<script type="module" crossorigin src="/webui/assets/index-Bz5MOBb9.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-CQhBIpFe.css">
</head>
<body>
<div id="root"></div>

View File

@@ -184,9 +184,11 @@ const PropertyRow = ({
return translation === translationKey ? name : translation
}
// Since Text component uses a label internally, we'll use a span here instead of a label
// to avoid nesting labels which is not recommended for accessibility
return (
<div className="flex items-center gap-2">
<label className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</label>:
<span className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</span>:
<Text
className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
tooltipClassName="max-w-80"
@@ -213,7 +215,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
return (
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center">
<label className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</label>
<h3 className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</h3>
<div className="flex gap-3">
<Button
size="icon"
@@ -246,7 +248,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
/>
<PropertyRow name={t('graphPanel.propertiesView.node.degree')} value={node.degree} />
</div>
<label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</label>
<h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</h3>
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
{Object.keys(node.properties)
.sort()
@@ -256,9 +258,9 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
</div>
{node.relationships.length > 0 && (
<>
<label className="text-md pl-1 font-bold tracking-wide text-emerald-700">
<h3 className="text-md pl-1 font-bold tracking-wide text-emerald-700">
{t('graphPanel.propertiesView.node.relationships')}
</label>
</h3>
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
{node.relationships.map(({ type, id, label }) => {
return (
@@ -283,7 +285,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
const { t } = useTranslation()
return (
<div className="flex flex-col gap-2">
<label className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</label>
<h3 className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</h3>
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
<PropertyRow name={t('graphPanel.propertiesView.edge.id')} value={edge.id} />
{edge.type && <PropertyRow name={t('graphPanel.propertiesView.edge.type')} value={edge.type} />}
@@ -302,7 +304,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
}}
/>
</div>
<label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</label>
<h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</h3>
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
{Object.keys(edge.properties)
.sort()

View File

@@ -23,11 +23,14 @@ const LabeledCheckBox = ({
onCheckedChange: () => void
label: string
}) => {
// Create unique ID using the label text converted to lowercase with spaces removed
const id = `checkbox-${label.toLowerCase().replace(/\s+/g, '-')}`;
return (
<div className="flex items-center gap-2">
<Checkbox checked={checked} onCheckedChange={onCheckedChange} />
<Checkbox id={id} checked={checked} onCheckedChange={onCheckedChange} />
<label
htmlFor="terms"
htmlFor={id}
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
@@ -56,6 +59,8 @@ const LabeledNumberInput = ({
}) => {
const { t } = useTranslation();
const [currentValue, setCurrentValue] = useState<number | null>(value)
// Create unique ID using the label text converted to lowercase with spaces removed
const id = `input-${label.toLowerCase().replace(/\s+/g, '-')}`;
const onValueChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -94,13 +99,14 @@ const LabeledNumberInput = ({
return (
<div className="flex flex-col gap-2">
<label
htmlFor="terms"
htmlFor={id}
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>
<div className="flex items-center gap-1">
<Input
id={id}
type="number"
value={currentValue === null ? '' : currentValue}
onChange={onValueChange}
@@ -295,11 +301,12 @@ export default function Settings() {
/>
<div className="flex flex-col gap-2">
<label className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
<label htmlFor="edge-size-min" className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{t('graphPanel.sideBar.settings.edgeSizeRange')}
</label>
<div className="flex items-center gap-2">
<Input
id="edge-size-min"
type="number"
value={minEdgeSize}
onChange={(e) => {
@@ -315,6 +322,7 @@ export default function Settings() {
<span>-</span>
<div className="flex items-center gap-1">
<Input
id="edge-size-max"
type="number"
value={maxEdgeSize}
onChange={(e) => {

View File

@@ -93,6 +93,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.topKTooltip')}
side="left"
/>
<div>
<label htmlFor="top_k" className="sr-only">
{t('retrievePanel.querySettings.topK')}
</label>
<NumberInput
id="top_k"
stepper={1}
@@ -101,6 +105,7 @@ export default function QuerySettings() {
min={1}
placeholder={t('retrievePanel.querySettings.topKPlaceholder')}
/>
</div>
</>
{/* Max Tokens */}
@@ -112,6 +117,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.maxTokensTextUnitTooltip')}
side="left"
/>
<div>
<label htmlFor="max_token_for_text_unit" className="sr-only">
{t('retrievePanel.querySettings.maxTokensTextUnit')}
</label>
<NumberInput
id="max_token_for_text_unit"
stepper={500}
@@ -120,6 +129,7 @@ export default function QuerySettings() {
min={1}
placeholder={t('retrievePanel.querySettings.maxTokensTextUnit')}
/>
</div>
</>
<>
@@ -128,6 +138,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.maxTokensGlobalContextTooltip')}
side="left"
/>
<div>
<label htmlFor="max_token_for_global_context" className="sr-only">
{t('retrievePanel.querySettings.maxTokensGlobalContext')}
</label>
<NumberInput
id="max_token_for_global_context"
stepper={500}
@@ -136,6 +150,7 @@ export default function QuerySettings() {
min={1}
placeholder={t('retrievePanel.querySettings.maxTokensGlobalContext')}
/>
</div>
</>
<>
@@ -145,6 +160,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.maxTokensLocalContextTooltip')}
side="left"
/>
<div>
<label htmlFor="max_token_for_local_context" className="sr-only">
{t('retrievePanel.querySettings.maxTokensLocalContext')}
</label>
<NumberInput
id="max_token_for_local_context"
stepper={500}
@@ -153,6 +172,7 @@ export default function QuerySettings() {
min={1}
placeholder={t('retrievePanel.querySettings.maxTokensLocalContext')}
/>
</div>
</>
</>
@@ -164,6 +184,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.historyTurnsTooltip')}
side="left"
/>
<div>
<label htmlFor="history_turns" className="sr-only">
{t('retrievePanel.querySettings.historyTurns')}
</label>
<NumberInput
className="!border-input"
id="history_turns"
@@ -174,6 +198,7 @@ export default function QuerySettings() {
min={0}
placeholder={t('retrievePanel.querySettings.historyTurnsPlaceholder')}
/>
</div>
</>
{/* Keywords */}
@@ -185,6 +210,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.hlKeywordsTooltip')}
side="left"
/>
<div>
<label htmlFor="hl_keywords" className="sr-only">
{t('retrievePanel.querySettings.hlKeywords')}
</label>
<Input
id="hl_keywords"
type="text"
@@ -198,6 +227,7 @@ export default function QuerySettings() {
}}
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
/>
</div>
</>
<>
@@ -207,6 +237,10 @@ export default function QuerySettings() {
tooltip={t('retrievePanel.querySettings.llKeywordsTooltip')}
side="left"
/>
<div>
<label htmlFor="ll_keywords" className="sr-only">
{t('retrievePanel.querySettings.llKeywords')}
</label>
<Input
id="ll_keywords"
type="text"
@@ -220,19 +254,21 @@ export default function QuerySettings() {
}}
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
/>
</div>
</>
</>
{/* Toggle Options */}
<>
<div className="flex items-center gap-2">
<label htmlFor="only_need_context" className="flex-1">
<Text
className="ml-1"
text={t('retrievePanel.querySettings.onlyNeedContext')}
tooltip={t('retrievePanel.querySettings.onlyNeedContextTooltip')}
side="left"
/>
<div className="grow" />
</label>
<Checkbox
className="mr-1 cursor-pointer"
id="only_need_context"
@@ -242,13 +278,14 @@ export default function QuerySettings() {
</div>
<div className="flex items-center gap-2">
<label htmlFor="only_need_prompt" className="flex-1">
<Text
className="ml-1"
text={t('retrievePanel.querySettings.onlyNeedPrompt')}
tooltip={t('retrievePanel.querySettings.onlyNeedPromptTooltip')}
side="left"
/>
<div className="grow" />
</label>
<Checkbox
className="mr-1 cursor-pointer"
id="only_need_prompt"
@@ -258,13 +295,14 @@ export default function QuerySettings() {
</div>
<div className="flex items-center gap-2">
<label htmlFor="stream" className="flex-1">
<Text
className="ml-1"
text={t('retrievePanel.querySettings.streamResponse')}
tooltip={t('retrievePanel.querySettings.streamResponseTooltip')}
side="left"
/>
<div className="grow" />
</label>
<Checkbox
className="mr-1 cursor-pointer"
id="stream"

View File

@@ -507,8 +507,14 @@ export default function DocumentManager() {
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
<label
htmlFor="toggle-filename-btn"
className="text-sm text-gray-500"
>
{t('documentPanel.documentManager.fileNameLabel')}
</label>
<Button
id="toggle-filename-btn"
variant="outline"
size="sm"
onClick={() => setShowFileName(!showFileName)}

View File

@@ -148,11 +148,11 @@ const LoginPage = () => {
<CardContent className="px-8 pb-8">
<form onSubmit={handleSubmit} className="space-y-6">
<div className="flex items-center gap-4">
<label htmlFor="username" className="text-sm font-medium w-16 shrink-0">
<label htmlFor="username-input" className="text-sm font-medium w-16 shrink-0">
{t('login.username')}
</label>
<Input
id="username"
id="username-input"
placeholder={t('login.usernamePlaceholder')}
value={username}
onChange={(e) => setUsername(e.target.value)}
@@ -161,11 +161,11 @@ const LoginPage = () => {
/>
</div>
<div className="flex items-center gap-4">
<label htmlFor="password" className="text-sm font-medium w-16 shrink-0">
<label htmlFor="password-input" className="text-sm font-medium w-16 shrink-0">
{t('login.password')}
</label>
<Input
id="password"
id="password-input"
type="password"
placeholder={t('login.passwordPlaceholder')}
value={password}

View File

@@ -147,13 +147,19 @@ export default function RetrievalTesting() {
<EraserIcon />
{t('retrievePanel.retrieval.clear')}
</Button>
<div className="flex-1 relative">
<label htmlFor="query-input" className="sr-only">
{t('retrievePanel.retrieval.placeholder')}
</label>
<Input
className="flex-1"
id="query-input"
className="w-full"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder={t('retrievePanel.retrieval.placeholder')}
disabled={isLoading}
/>
</div>
<Button type="submit" variant="default" disabled={isLoading} size="sm">
<SendIcon />
{t('retrievePanel.retrieval.send')}

View File

@@ -296,11 +296,11 @@
"queryMode": "查询模式",
"queryModeTooltip": "选择检索策略:\n• Naive基础搜索无高级技术\n• Local上下文相关信息检索\n• Global利用全局知识库\n• Hybrid结合本地和全局检索\n• Mix整合知识图谱和向量检索",
"queryModeOptions": {
"naive": "朴素",
"local": "本地",
"global": "全局",
"hybrid": "混合",
"mix": "混合"
"naive": "Naive",
"local": "Local",
"global": "Global",
"hybrid": "Hybrid",
"mix": "Mix"
},
"responseFormat": "响应格式",
"responseFormatTooltip": "定义响应格式。例如:\n• 多段落\n• 单段落\n• 要点",