mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 20:20:57 +00:00
feat(cli): Increase /insight feature exposure via weighted tips
- Add weighted tip system to make certain tips appear more frequently - Set /insight tip to weight 3 (3x more likely than regular tips) - Add i18n translations for the new tip across all supported languages - Add comprehensive unit tests for weighted tip selection Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
e93b287329
commit
1b7d153a4c
8 changed files with 150 additions and 6 deletions
62
packages/cli/src/ui/components/Tips.test.ts
Normal file
62
packages/cli/src/ui/components/Tips.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { selectWeightedTip } from './Tips.js';
|
||||
|
||||
describe('selectWeightedTip', () => {
|
||||
const tips = [
|
||||
{ text: 'tip-a', weight: 1 },
|
||||
{ text: 'tip-b', weight: 3 },
|
||||
{ text: 'tip-c', weight: 1 },
|
||||
];
|
||||
|
||||
it('returns a valid tip text', () => {
|
||||
const result = selectWeightedTip(tips);
|
||||
expect(['tip-a', 'tip-b', 'tip-c']).toContain(result);
|
||||
});
|
||||
|
||||
it('selects the first tip when random is near zero', () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
expect(selectWeightedTip(tips)).toBe('tip-a');
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('selects the weighted tip when random falls in its range', () => {
|
||||
// Total weight = 5. tip-a covers [0,1), tip-b covers [1,4), tip-c covers [4,5)
|
||||
// Math.random() * 5 = 2.0 falls in tip-b's range
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.4); // 0.4 * 5 = 2.0
|
||||
expect(selectWeightedTip(tips)).toBe('tip-b');
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('selects the last tip when random is near max', () => {
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.99);
|
||||
expect(selectWeightedTip(tips)).toBe('tip-c');
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('respects weight distribution over many samples', () => {
|
||||
const counts: Record<string, number> = {
|
||||
'tip-a': 0,
|
||||
'tip-b': 0,
|
||||
'tip-c': 0,
|
||||
};
|
||||
const iterations = 10000;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const result = selectWeightedTip(tips);
|
||||
counts[result]!++;
|
||||
}
|
||||
// tip-b (weight 3) should appear roughly 3x as often as tip-a or tip-c (weight 1)
|
||||
// With 10k iterations, we expect: tip-a ~2000, tip-b ~6000, tip-c ~2000
|
||||
expect(counts['tip-b']!).toBeGreaterThan(counts['tip-a']! * 2);
|
||||
expect(counts['tip-b']!).toBeGreaterThan(counts['tip-c']! * 2);
|
||||
});
|
||||
|
||||
it('handles single tip', () => {
|
||||
expect(selectWeightedTip([{ text: 'only', weight: 1 }])).toBe('only');
|
||||
});
|
||||
});
|
||||
|
|
@ -9,7 +9,9 @@ import { Box, Text } from 'ink';
|
|||
import { theme } from '../semantic-colors.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
const startupTips = [
|
||||
type Tip = string | { text: string; weight: number };
|
||||
|
||||
const startupTips: Tip[] = [
|
||||
'Use /compress when the conversation gets long to summarize history and free up context.',
|
||||
'Start a fresh idea with /clear or /new; the previous session stays available in history.',
|
||||
'Use /bug to submit issues to the maintainers when something goes off.',
|
||||
|
|
@ -20,13 +22,34 @@ const startupTips = [
|
|||
process.platform === 'win32'
|
||||
? 'You can switch permission mode quickly with Tab or /approval-mode.'
|
||||
: 'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
|
||||
] as const;
|
||||
{
|
||||
text: 'Try /insight to generate personalized insights from your chat history.',
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
function tipText(tip: Tip): string {
|
||||
return typeof tip === 'string' ? tip : tip.text;
|
||||
}
|
||||
|
||||
function tipWeight(tip: Tip): number {
|
||||
return typeof tip === 'string' ? 1 : tip.weight;
|
||||
}
|
||||
|
||||
export function selectWeightedTip(tips: Tip[]): string {
|
||||
const totalWeight = tips.reduce((sum, tip) => sum + tipWeight(tip), 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
for (const tip of tips) {
|
||||
random -= tipWeight(tip);
|
||||
if (random <= 0) {
|
||||
return tipText(tip);
|
||||
}
|
||||
}
|
||||
return tipText(tips[tips.length - 1]!);
|
||||
}
|
||||
|
||||
export const Tips: React.FC = () => {
|
||||
const selectedTip = useMemo(() => {
|
||||
const randomIndex = Math.floor(Math.random() * startupTips.length);
|
||||
return startupTips[randomIndex];
|
||||
}, []);
|
||||
const selectedTip = useMemo(() => selectWeightedTip(startupTips), []);
|
||||
|
||||
return (
|
||||
<Box marginLeft={2} marginRight={2}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue