Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/cli/src/i18n/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -1380,10 +1380,26 @@ export default {
'Erweiterungsseite wird im Browser geöffnet: {{url}}',
'Failed to open browser. Check out the extensions gallery at {{url}}':
'Browser konnte nicht geöffnet werden. Besuchen Sie die Erweiterungsgalerie unter {{url}}',
'Use /compress when the conversation gets long to summarize history and free up context.':
'Verwenden Sie /compress, wenn die Unterhaltung lang wird, um den Verlauf zusammenzufassen und Kontext freizugeben.',
'Start a fresh idea with /clear or /new; the previous session stays available in history.':
'Starten Sie eine neue Idee mit /clear oder /new; die vorherige Sitzung bleibt im Verlauf verfügbar.',
'Use /bug to submit issues to the maintainers when something goes off.':
'Verwenden Sie /bug, um Probleme an die Betreuer zu melden, wenn etwas schiefgeht.',
'Switch auth type quickly with /auth.':
'Wechseln Sie den Authentifizierungstyp schnell mit /auth.',
'You can run any shell commands from Qwen Code using ! (e.g. !ls).':
'Sie können beliebige Shell-Befehle in Qwen Code mit ! ausführen (z. B. !ls).',
'Type / to open the command popup; Tab autocompletes slash commands and saved prompts.':
'Geben Sie / ein, um das Befehlsmenü zu öffnen; Tab vervollständigt Slash-Befehle und gespeicherte Prompts.',
'You can resume a previous conversation by running qwen --continue or qwen --resume.':
'Sie können eine frühere Unterhaltung mit qwen --continue oder qwen --resume fortsetzen.',
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
'Sie können den Berechtigungsmodus schnell mit Shift+Tab oder /approval-mode wechseln.',
'You can switch permission mode quickly with Tab or /approval-mode.':
'Sie können den Berechtigungsmodus schnell mit Tab oder /approval-mode wechseln.',
'Try /insight to generate personalized insights from your chat history.':
'Probieren Sie /insight, um personalisierte Erkenntnisse aus Ihrem Chatverlauf zu erstellen.',

// ============================================================================
// Custom API-KEY Configuration
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,8 @@ export default {
'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
'You can switch permission mode quickly with Tab or /approval-mode.':
'You can switch permission mode quickly with Tab or /approval-mode.',
'Try /insight to generate personalized insights from your chat history.':
'Try /insight to generate personalized insights from your chat history.',

// ============================================================================
// Exit Screen / Stats
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/i18n/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,27 @@ export default {
"Starting OAuth authentication for MCP server '{{name}}'...":
"MCPサーバー '{{name}}' のOAuth認証を開始中...",
// Startup Tips
'Tips:': 'ヒント:',
'Use /compress when the conversation gets long to summarize history and free up context.':
'会話が長くなったら /compress で履歴を要約し、コンテキストを解放できます。',
'Start a fresh idea with /clear or /new; the previous session stays available in history.':
'/clear または /new で新しいアイデアを始められます。前のセッションは履歴に残ります。',
'Use /bug to submit issues to the maintainers when something goes off.':
'問題が発生したら /bug でメンテナーに報告できます。',
'Switch auth type quickly with /auth.':
'/auth で認証タイプをすばやく切り替えられます。',
'You can run any shell commands from Qwen Code using ! (e.g. !ls).':
'Qwen Code から ! を使って任意のシェルコマンドを実行できます(例: !ls)。',
'Type / to open the command popup; Tab autocompletes slash commands and saved prompts.':
'/ を入力してコマンドポップアップを開きます。Tab でスラッシュコマンドと保存済みプロンプトを補完できます。',
'You can resume a previous conversation by running qwen --continue or qwen --resume.':
'qwen --continue または qwen --resume で前の会話を再開できます。',
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
'Shift+Tab または /approval-mode で権限モードをすばやく切り替えられます。',
'You can switch permission mode quickly with Tab or /approval-mode.':
'Tab または /approval-mode で権限モードをすばやく切り替えられます。',
'Try /insight to generate personalized insights from your chat history.':
'/insight でチャット履歴からパーソナライズされたインサイトを生成できます。',
'Tips for getting started:': '始めるためのヒント:',
'1. Ask questions, edit files, or run commands.':
'1. 質問したり、ファイルを編集したり、コマンドを実行したりできます',
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/i18n/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,8 @@ export default {
'Você pode retomar uma conversa anterior executando qwen --continue ou qwen --resume.',
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
'Você pode alternar o modo de permissão rapidamente com Shift+Tab ou /approval-mode.',
'Try /insight to generate personalized insights from your chat history.':
'Experimente /insight para gerar insights personalizados do seu histórico de conversas.',

// ============================================================================
// Exit Screen / Stats
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/i18n/locales/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -1384,10 +1384,26 @@ export default {
'Открываем страницу расширений в браузере: {{url}}',
'Failed to open browser. Check out the extensions gallery at {{url}}':
'Не удалось открыть браузер. Посетите галерею расширений по адресу {{url}}',
'Use /compress when the conversation gets long to summarize history and free up context.':
'Используйте /compress, когда разговор становится длинным, чтобы подвести итог и освободить контекст.',
'Start a fresh idea with /clear or /new; the previous session stays available in history.':
'Начните новую идею с /clear или /new; предыдущая сессия останется в истории.',
'Use /bug to submit issues to the maintainers when something goes off.':
'Используйте /bug, чтобы сообщить о проблемах разработчикам.',
'Switch auth type quickly with /auth.':
'Быстро переключите тип аутентификации с помощью /auth.',
'You can run any shell commands from Qwen Code using ! (e.g. !ls).':
'Вы можете выполнять любые shell-команды в Qwen Code с помощью ! (например, !ls).',
'Type / to open the command popup; Tab autocompletes slash commands and saved prompts.':
'Введите /, чтобы открыть меню команд; Tab автодополняет слэш-команды и сохранённые промпты.',
'You can resume a previous conversation by running qwen --continue or qwen --resume.':
'Вы можете продолжить предыдущий разговор, запустив qwen --continue или qwen --resume.',
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
'Вы можете быстро переключать режим разрешений с помощью Shift+Tab или /approval-mode.',
'You can switch permission mode quickly with Tab or /approval-mode.':
'Вы можете быстро переключать режим разрешений с помощью Tab или /approval-mode.',
'Try /insight to generate personalized insights from your chat history.':
'Попробуйте /insight, чтобы получить персонализированные выводы из истории чатов.',

// ============================================================================
// Custom API-KEY Configuration
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/i18n/locales/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,8 @@ export default {
'按 Shift+Tab 或输入 /approval-mode 可快速切换权限模式。',
'You can switch permission mode quickly with Tab or /approval-mode.':
'按 Tab 或输入 /approval-mode 可快速切换权限模式。',
'Try /insight to generate personalized insights from your chat history.':
'试试 /insight,从聊天记录中生成个性化洞察。',

// ============================================================================
// Exit Screen / Stats
Expand Down
62 changes: 62 additions & 0 deletions packages/cli/src/ui/components/Tips.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
35 changes: 29 additions & 6 deletions packages/cli/src/ui/components/Tips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand All @@ -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}>
Expand Down