Merge pull request #1291 from danielaskdd/main
fix: improve form accessibility with proper label associations
This commit is contained in:
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/index-CQhBIpFe.css
generated
Normal file
1
lightrag/api/webui/assets/index-CQhBIpFe.css
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/index-QU59h9JG.css
generated
1
lightrag/api/webui/assets/index-QU59h9JG.css
generated
File diff suppressed because one or more lines are too long
4
lightrag/api/webui/index.html
generated
4
lightrag/api/webui/index.html
generated
@@ -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>
|
||||
|
@@ -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()
|
||||
|
@@ -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) => {
|
||||
|
@@ -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"
|
||||
|
@@ -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)}
|
||||
|
@@ -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}
|
||||
|
@@ -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')}
|
||||
|
@@ -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• 要点",
|
||||
|
Reference in New Issue
Block a user