mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
BIN
ccw/frontend/settings-full-page.png
Normal file
BIN
ccw/frontend/settings-full-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 750 KiB |
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -751,6 +751,7 @@ Type inference: options + multiSelect=true → multi-select; options + multiSele
|
||||
properties: {
|
||||
label: { type: 'string', description: 'Display text, also used as value' },
|
||||
description: { type: 'string', description: 'Option description' },
|
||||
isDefault: { type: 'boolean', description: 'Mark as default/recommended option' },
|
||||
},
|
||||
required: ['label'],
|
||||
},
|
||||
@@ -830,6 +831,7 @@ interface PageMeta {
|
||||
function generateMultiQuestionSurface(
|
||||
questions: SimpleQuestion[],
|
||||
surfaceId: string,
|
||||
timeoutMs: number,
|
||||
): {
|
||||
surfaceUpdate: {
|
||||
surfaceId: string;
|
||||
@@ -997,6 +999,7 @@ function generateMultiQuestionSurface(
|
||||
questionType: 'multi-question',
|
||||
pages,
|
||||
totalPages: questions.length,
|
||||
timeoutAt: new Date(Date.now() + timeoutMs).toISOString(),
|
||||
},
|
||||
displayMode: 'popup',
|
||||
},
|
||||
@@ -1055,7 +1058,7 @@ async function executeSimpleFormat(
|
||||
const compositeId = `multi-${Date.now()}`;
|
||||
const surfaceId = `question-${compositeId}`;
|
||||
|
||||
const { surfaceUpdate, pages } = generateMultiQuestionSurface(questions, surfaceId);
|
||||
const { surfaceUpdate, pages } = generateMultiQuestionSurface(questions, surfaceId, timeout ?? DEFAULT_TIMEOUT_MS);
|
||||
|
||||
// Create promise for the composite answer
|
||||
const resultPromise = new Promise<AskQuestionResult>((resolve, reject) => {
|
||||
|
||||
Reference in New Issue
Block a user