Refactor code structure for improved readability and maintainability

This commit is contained in:
catlog22
2026-02-15 23:35:48 +08:00
parent 8938c47f88
commit dc03862ca7
18 changed files with 384 additions and 3743 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

View File

@@ -5,7 +5,7 @@
// Used for displayMode: 'popup' surfaces (e.g., ask_question)
// Supports markdown content parsing and multi-page navigation
import { useState, useCallback, useMemo } from 'react';
import { useState, useCallback, useMemo, useEffect } from 'react';
import { useIntl } from 'react-intl';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
@@ -167,6 +167,23 @@ function SinglePagePopup({ surface, onClose }: A2UIPopupCardProps) {
const questionId = (surface.initialState as any)?.questionId as string | undefined;
// Countdown timer for auto-selection
const timeoutAt = (surface.initialState as any)?.timeoutAt as string | undefined;
const defaultLabel = (surface.initialState as any)?.defaultValue as string | undefined;
const [remaining, setRemaining] = useState<number | null>(null);
useEffect(() => {
if (!timeoutAt || !defaultLabel) return;
const target = new Date(timeoutAt).getTime();
const tick = () => {
const secs = Math.max(0, Math.ceil((target - Date.now()) / 1000));
setRemaining(secs);
};
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [timeoutAt, defaultLabel]);
// Extract title, message, and description from surface components
const titleComponent = surface.components.find(
(c) => c.id === 'title' && 'Text' in (c.component as any)
@@ -351,6 +368,15 @@ function SinglePagePopup({ surface, onClose }: A2UIPopupCardProps) {
</div>
)}
{/* Countdown for auto-selection */}
{remaining !== null && defaultLabel && (
<div className="text-xs text-muted-foreground text-center pt-2">
{remaining > 0
? `${remaining}s 后将自动选择「${defaultLabel}`
: `即将自动选择「${defaultLabel}`}
</div>
)}
{/* Footer - Action buttons */}
{actionButtons.length > 0 && (
<DialogFooter className="pt-4">
@@ -383,6 +409,22 @@ function MultiPagePopup({ surface, onClose }: A2UIPopupCardProps) {
const [currentPage, setCurrentPage] = useState(0);
// Countdown timer for auto-selection
const timeoutAt = (state as any)?.timeoutAt as string | undefined;
const [remaining, setRemaining] = useState<number | null>(null);
useEffect(() => {
if (!timeoutAt) return;
const target = new Date(timeoutAt).getTime();
const tick = () => {
const secs = Math.max(0, Math.ceil((target - Date.now()) / 1000));
setRemaining(secs);
};
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [timeoutAt]);
// "Other" per-page state
const [otherSelectedPages, setOtherSelectedPages] = useState<Set<number>>(new Set());
const [otherTexts, setOtherTexts] = useState<Map<number, string>>(new Map());
@@ -613,6 +655,15 @@ function MultiPagePopup({ surface, onClose }: A2UIPopupCardProps) {
))}
</div>
{/* Countdown for auto-selection */}
{remaining !== null && (
<div className="text-xs text-muted-foreground text-center">
{remaining > 0
? `${remaining}s 后将自动提交默认选项`
: '即将自动提交默认选项'}
</div>
)}
{/* Footer - Navigation buttons */}
<DialogFooter className="pt-2">
<div className="flex flex-row justify-between w-full">

View File

@@ -127,6 +127,7 @@ export const RadioGroupComponentSchema = z.object({
label: TextContentSchema,
value: z.string(),
description: TextContentSchema.optional(),
isDefault: z.boolean().optional(),
})),
selectedValue: TextContentSchema.optional(),
onChange: ActionSchema,

View File

@@ -43,7 +43,9 @@ export const A2UIRadioGroup: ComponentRenderer = ({ component, onAction, resolve
return (
<RadioGroup value={selectedValue} onValueChange={handleChange} className="space-y-2">
{radioConfig.options.map((option, idx) => {
const labelText = resolveTextContent(option.label, resolveBinding);
const rawLabel = resolveTextContent(option.label, resolveBinding);
const labelText = rawLabel.replace(/\s*\(Recommended\)\s*/i, '');
const isDefault = (option as any).isDefault === true || /\(Recommended\)/i.test(rawLabel);
const descriptionText = option.description
? resolveTextContent(option.description, resolveBinding)
: undefined;
@@ -61,6 +63,11 @@ export const A2UIRadioGroup: ComponentRenderer = ({ component, onAction, resolve
className="text-sm font-medium leading-none cursor-pointer"
>
{labelText}
{isDefault && (
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-primary/10 text-primary">
</span>
)}
</Label>
{descriptionText && (
<span className="text-xs text-muted-foreground mt-1">