feat: Refactor feedback dialog logic into a custom hook

This commit is contained in:
DragonnZhang 2026-01-12 13:10:43 +08:00
parent f7585153b7
commit d095a8b3f1
2 changed files with 159 additions and 121 deletions

View file

@ -37,9 +37,6 @@ import {
getErrorMessage,
getAllGeminiMdFilenames,
ShellExecutionService,
logUserFeedback,
UserFeedbackEvent,
type UserFeedbackRating,
} from '@qwen-code/qwen-code-core';
import { buildResumedHistoryItems } from './utils/resumeHistoryUtils.js';
import { validateAuthMethod } from '../config/auth.js';
@ -48,6 +45,7 @@ import process from 'node:process';
import { useHistory } from './hooks/useHistoryManager.js';
import { useMemoryMonitor } from './hooks/useMemoryMonitor.js';
import { useThemeCommand } from './hooks/useThemeCommand.js';
import { useFeedbackDialog } from './hooks/useFeedbackDialog.js';
import { useAuthCommand } from './auth/useAuth.js';
import { useEditorSettings } from './hooks/useEditorSettings.js';
import { useSettingsCommand } from './hooks/useSettingsCommand.js';
@ -185,18 +183,6 @@ export const AppContainer = (props: AppContainerProps) => {
// Helper to determine the current model (polled, since Config has no model-change event).
const getCurrentModel = useCallback(() => config.getModel(), [config]);
// Feedback dialog state
const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false);
const [feedbackShownForSession, setFeedbackShownForSession] = useState(false);
const openFeedbackDialog = useCallback(() => {
setIsFeedbackDialogOpen(true);
setFeedbackShownForSession(true);
}, []);
const closeFeedbackDialog = useCallback(
() => setIsFeedbackDialogOpen(false),
[],
);
const [currentModel, setCurrentModel] = useState(getCurrentModel());
const [isConfigInitialized, setConfigInitialized] = useState(false);
@ -213,40 +199,6 @@ export const AppContainer = (props: AppContainerProps) => {
const logger = useLogger(config.storage, sessionStats.sessionId);
const branchName = useGitBranchName(config.getTargetDir());
// Submit user feedback function
const submitFeedback = useCallback(
(rating: number) => {
// Calculate session duration and turn count
const sessionDurationMs =
Date.now() - sessionStats.sessionStartTime.getTime();
let lastUserMessageIndex = -1;
for (let i = historyManager.history.length - 1; i >= 0; i--) {
if (historyManager.history[i].type === MessageType.USER) {
lastUserMessageIndex = i;
break;
}
}
const turnCount =
lastUserMessageIndex === -1
? 0
: historyManager.history.length - lastUserMessageIndex;
// Create and log the feedback event
const feedbackEvent = new UserFeedbackEvent(
sessionStats.sessionId,
rating as UserFeedbackRating,
sessionDurationMs,
turnCount,
config.getModel(),
config.getApprovalMode(),
);
logUserFeedback(config, feedbackEvent);
closeFeedbackDialog();
},
[sessionStats, historyManager.history, config, closeFeedbackDialog],
);
// Layout measurements
const mainControlsRef = useRef<DOMElement>(null);
const originalTitleRef = useRef(
@ -1222,6 +1174,19 @@ export const AppContainer = (props: AppContainerProps) => {
const nightly = props.version.includes('nightly');
const {
isFeedbackDialogOpen,
openFeedbackDialog,
closeFeedbackDialog,
submitFeedback,
} = useFeedbackDialog({
config,
settings,
streamingState,
history: historyManager.history,
sessionStats,
});
const dialogsVisible =
showWelcomeBackDialog ||
showWorkspaceMigrationDialog ||
@ -1246,78 +1211,6 @@ export const AppContainer = (props: AppContainerProps) => {
isResumeDialogOpen ||
isFeedbackDialogOpen;
// Track when to show feedback dialog
useEffect(() => {
if (
streamingState === StreamingState.Idle &&
historyManager.history.length > 0
) {
// Find the last user message and check if there's AI response after it
let lastUserMessageIndex = -1;
let hasAIResponseAfterLastUser = false;
for (let i = historyManager.history.length - 1; i >= 0; i--) {
if (historyManager.history[i].type === MessageType.USER) {
lastUserMessageIndex = i;
break;
}
}
// Check if there's any AI response (GEMINI message) after the last user message
if (lastUserMessageIndex !== -1) {
for (
let i = lastUserMessageIndex + 1;
i < historyManager.history.length;
i++
) {
if (historyManager.history[i].type === MessageType.GEMINI) {
hasAIResponseAfterLastUser = true;
break;
}
}
}
const sessionDurationMs =
Date.now() - sessionStats.sessionStartTime.getTime();
// Show feedback dialog if:
// 1. Telemetry is enabled (required for feedback submission)
// 2. User feedback is enabled in settings
// 3. There's an AI response after the last user message (real AI conversation)
// 4. Session duration > 10 seconds (meaningful interaction)
// 5. No other dialogs are open
// 6. Not already shown for this session
// 7. Random chance (25% probability)
if (
config.getUsageStatisticsEnabled() && // Only show if telemetry is enabled
settings.merged.ui?.enableUserFeedback !== false && // Default to true if not set
hasAIResponseAfterLastUser &&
sessionDurationMs > 10000 && // 10 seconds minimum for meaningful interaction
!dialogsVisible &&
!isFeedbackDialogOpen &&
!feedbackShownForSession &&
Math.random() < 0.25 // 25% probability
) {
setTimeout(() => {
// Double check no dialogs opened in the meantime
if (!dialogsVisible && !isFeedbackDialogOpen) {
openFeedbackDialog();
}
}, 1000); // Delay to ensure user has time to see the completion
}
}
}, [
streamingState,
historyManager.history,
sessionStats,
dialogsVisible,
isFeedbackDialogOpen,
feedbackShownForSession,
openFeedbackDialog,
settings.merged.ui?.enableUserFeedback,
config,
]);
const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],