mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
feat: add contextual tips system with post-response context awareness (#2904)
* feat: add contextual tips system with post-response context awareness Add a context-aware tips system that proactively shows helpful tips based on session state. Post-response tips warn when context usage exceeds 80% or 95%, suggesting /compress. Startup tips rotate across sessions via LRU scheduling with cross-session persistence (~/.qwen/tip_history.json). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use value import for runtime values in useContextualTips Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback - Use lastSessionTimestamp instead of totalShown for cross-session LRU - Move getTipHistory singleton from Tips.tsx to services/tips/index.ts - Defer TipHistory.load() when hideTips is true (no side effects) - Use os.tmpdir() in tests for cross-platform portability - Add proper translations for de/ja/pt/ru locale files - Accept TipHistory | null in useContextualTips Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review feedback - Validate tips field type in TipHistory.load() to handle corrupted JSON - Split approval-mode tip into platform-specific variants using ctx.platform - Add afterEach cleanup for temp files in all test suites - Guard useContextualTips against null tipHistory Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: import shared DEFAULT_TOKEN_LIMIT, harden tipHistory, set file permissions - Import DEFAULT_TOKEN_LIMIT from @qwen-code/qwen-code-core instead of hardcoding 1_048_576 in tipRegistry.ts and useContextualTips.ts - Add normalizeEntry() to defensively handle corrupted tip history entries - Write tip_history.json with mode 0o600 for privacy on multi-user systems Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove unused compressionThreshold from TipContext compressionThreshold was defined in TipContext but never used by any tip's isRelevant check. Remove it to avoid misleading consumers into thinking tips respect the user's compression settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sanitize sessionCount and getLastShown against corrupted tip history - Validate sessionCount is finite and non-negative in TipHistory.load() - Use normalizeEntry() in getLastShown() for corrupted lastSessionTimestamp Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add contextual tips user documentation Add docs/users/features/tips.md covering startup tips, post-response context warnings, tip history persistence, and the hideTips setting. Update settings.md description and register the new page in _meta.ts. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6af0f37bb8
commit
b3bc42931e
19 changed files with 1147 additions and 79 deletions
106
packages/cli/src/ui/hooks/useContextualTips.ts
Normal file
106
packages/cli/src/ui/hooks/useContextualTips.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hook that evaluates contextual tips after each model response
|
||||
* and injects them as INFO messages into the conversation history.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { type Config, DEFAULT_TOKEN_LIMIT } from '@qwen-code/qwen-code-core';
|
||||
import {
|
||||
StreamingState,
|
||||
MessageType,
|
||||
type HistoryItemWithoutId,
|
||||
} from '../types.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import {
|
||||
selectTip,
|
||||
tipRegistry,
|
||||
type TipContext,
|
||||
type TipHistory,
|
||||
} from '../../services/tips/index.js';
|
||||
|
||||
interface UseContextualTipsOptions {
|
||||
streamingState: StreamingState;
|
||||
lastPromptTokenCount: number;
|
||||
sessionPromptCount: number;
|
||||
config: Config;
|
||||
tipHistory: TipHistory | null;
|
||||
addItem: (item: HistoryItemWithoutId, timestamp: number) => void;
|
||||
hideTips: boolean;
|
||||
}
|
||||
|
||||
export function useContextualTips({
|
||||
streamingState,
|
||||
lastPromptTokenCount,
|
||||
sessionPromptCount,
|
||||
config,
|
||||
tipHistory,
|
||||
addItem,
|
||||
hideTips,
|
||||
}: UseContextualTipsOptions): void {
|
||||
const prevStreamingState = useRef<StreamingState>(StreamingState.Idle);
|
||||
// Track whether the model was responding at any point before going idle,
|
||||
// so we catch Responding → WaitingForConfirmation → Idle transitions too.
|
||||
const hadResponsePhase = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
streamingState === StreamingState.Responding ||
|
||||
(prevStreamingState.current === StreamingState.Responding &&
|
||||
streamingState === StreamingState.WaitingForConfirmation)
|
||||
) {
|
||||
hadResponsePhase.current = true;
|
||||
}
|
||||
|
||||
const isNowIdle = streamingState === StreamingState.Idle;
|
||||
prevStreamingState.current = streamingState;
|
||||
|
||||
// Only evaluate tips when transitioning to Idle after a response phase
|
||||
if (!hadResponsePhase.current || !isNowIdle) {
|
||||
return;
|
||||
}
|
||||
// Reset regardless of hideTips to prevent stale state accumulation
|
||||
hadResponsePhase.current = false;
|
||||
|
||||
if (hideTips || !tipHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentGeneratorConfig = config.getContentGeneratorConfig();
|
||||
const contextWindowSize =
|
||||
contentGeneratorConfig?.contextWindowSize ?? DEFAULT_TOKEN_LIMIT;
|
||||
|
||||
const tipContext: TipContext = {
|
||||
lastPromptTokenCount,
|
||||
contextWindowSize,
|
||||
sessionPromptCount,
|
||||
sessionCount: tipHistory.sessionCount,
|
||||
platform: process.platform,
|
||||
};
|
||||
|
||||
const tip = selectTip('post-response', tipContext, tipRegistry, tipHistory);
|
||||
if (tip) {
|
||||
tipHistory.recordShown(tip.id, sessionPromptCount);
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: `💡 ${t(tip.content)}`,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
}, [
|
||||
streamingState,
|
||||
lastPromptTokenCount,
|
||||
sessionPromptCount,
|
||||
config,
|
||||
tipHistory,
|
||||
addItem,
|
||||
hideTips,
|
||||
]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue