mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,117 +1,170 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js';
|
||||
import { getTimestampedHistory, type TimestampedHistoryEntry } from '../history.js';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { stringWidth } from '../ink/stringWidth.js';
|
||||
import { wrapAnsi } from '../ink/wrapAnsi.js';
|
||||
import { Box, Text } from '../ink.js';
|
||||
import { logEvent } from '../services/analytics/index.js';
|
||||
import type { HistoryEntry } from '../utils/config.js';
|
||||
import { formatRelativeTimeAgo, truncateToWidth } from '../utils/format.js';
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useRegisterOverlay } from '../context/overlayContext.js'
|
||||
import {
|
||||
getTimestampedHistory,
|
||||
type TimestampedHistoryEntry,
|
||||
} from '../history.js'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js'
|
||||
import { stringWidth } from '../ink/stringWidth.js'
|
||||
import { wrapAnsi } from '../ink/wrapAnsi.js'
|
||||
import { Box, Text } from '../ink.js'
|
||||
import { logEvent } from '../services/analytics/index.js'
|
||||
import type { HistoryEntry } from '../utils/config.js'
|
||||
import { formatRelativeTimeAgo, truncateToWidth } from '../utils/format.js'
|
||||
import { FuzzyPicker } from './design-system/FuzzyPicker.js'
|
||||
|
||||
type Props = {
|
||||
initialQuery?: string;
|
||||
onSelect: (entry: HistoryEntry) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
const PREVIEW_ROWS = 6;
|
||||
const AGE_WIDTH = 8;
|
||||
initialQuery?: string
|
||||
onSelect: (entry: HistoryEntry) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const PREVIEW_ROWS = 6
|
||||
const AGE_WIDTH = 8
|
||||
|
||||
type Item = {
|
||||
entry: TimestampedHistoryEntry;
|
||||
display: string;
|
||||
lower: string;
|
||||
firstLine: string;
|
||||
age: string;
|
||||
};
|
||||
entry: TimestampedHistoryEntry
|
||||
display: string
|
||||
lower: string
|
||||
firstLine: string
|
||||
age: string
|
||||
}
|
||||
|
||||
export function HistorySearchDialog({
|
||||
initialQuery,
|
||||
onSelect,
|
||||
onCancel
|
||||
onCancel,
|
||||
}: Props): React.ReactNode {
|
||||
useRegisterOverlay('history-search', undefined);
|
||||
const {
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
const [items, setItems] = useState<Item[] | null>(null);
|
||||
const [query, setQuery] = useState(initialQuery ?? '');
|
||||
useRegisterOverlay('history-search')
|
||||
const { columns } = useTerminalSize()
|
||||
|
||||
const [items, setItems] = useState<Item[] | null>(null)
|
||||
const [query, setQuery] = useState(initialQuery ?? '')
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let cancelled = false
|
||||
void (async () => {
|
||||
const reader = getTimestampedHistory();
|
||||
const loaded: Item[] = [];
|
||||
const reader = getTimestampedHistory()
|
||||
const loaded: Item[] = []
|
||||
for await (const entry of reader) {
|
||||
if (cancelled) {
|
||||
void reader.return(undefined);
|
||||
return;
|
||||
void reader.return(undefined)
|
||||
return
|
||||
}
|
||||
const display = entry.display;
|
||||
const nl = display.indexOf('\n');
|
||||
const age = formatRelativeTimeAgo(new Date(entry.timestamp));
|
||||
const display = entry.display
|
||||
const nl = display.indexOf('\n')
|
||||
const age = formatRelativeTimeAgo(new Date(entry.timestamp))
|
||||
loaded.push({
|
||||
entry,
|
||||
display,
|
||||
lower: display.toLowerCase(),
|
||||
firstLine: nl === -1 ? display : display.slice(0, nl),
|
||||
age: age + ' '.repeat(Math.max(0, AGE_WIDTH - stringWidth(age)))
|
||||
});
|
||||
age: age + ' '.repeat(Math.max(0, AGE_WIDTH - stringWidth(age))),
|
||||
})
|
||||
}
|
||||
if (!cancelled) setItems(loaded);
|
||||
})();
|
||||
if (!cancelled) setItems(loaded)
|
||||
})()
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!items) return [];
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return items;
|
||||
const exact: Item[] = [];
|
||||
const fuzzy: Item[] = [];
|
||||
if (!items) return []
|
||||
const q = query.trim().toLowerCase()
|
||||
if (!q) return items
|
||||
const exact: Item[] = []
|
||||
const fuzzy: Item[] = []
|
||||
for (const item of items) {
|
||||
if (item.lower.includes(q)) {
|
||||
exact.push(item);
|
||||
exact.push(item)
|
||||
} else if (isSubsequence(item.lower, q)) {
|
||||
fuzzy.push(item);
|
||||
fuzzy.push(item)
|
||||
}
|
||||
}
|
||||
return exact.concat(fuzzy);
|
||||
}, [items, query]);
|
||||
const previewOnRight = columns >= 100;
|
||||
const listWidth = previewOnRight ? Math.floor((columns - 6) * 0.5) : columns - 6;
|
||||
const rowWidth = Math.max(20, listWidth - AGE_WIDTH - 1);
|
||||
const previewWidth = previewOnRight ? Math.max(20, columns - listWidth - 12) : Math.max(20, columns - 10);
|
||||
return <FuzzyPicker title="Search prompts" placeholder="Filter history…" initialQuery={initialQuery} items={filtered} getKey={item_0 => String(item_0.entry.timestamp)} onQueryChange={setQuery} onSelect={item_1 => {
|
||||
logEvent('tengu_history_picker_select', {
|
||||
result_count: filtered.length,
|
||||
query_length: query.length
|
||||
});
|
||||
void item_1.entry.resolve().then(onSelect);
|
||||
}} onCancel={onCancel} emptyMessage={q_0 => items === null ? 'Loading…' : q_0 ? 'No matching prompts' : 'No history yet'} selectAction="use" direction="up" previewPosition={previewOnRight ? 'right' : 'bottom'} renderItem={(item_2, isFocused) => <Text>
|
||||
<Text dimColor>{item_2.age}</Text>
|
||||
return exact.concat(fuzzy)
|
||||
}, [items, query])
|
||||
|
||||
const previewOnRight = columns >= 100
|
||||
const listWidth = previewOnRight
|
||||
? Math.floor((columns - 6) * 0.5)
|
||||
: columns - 6
|
||||
const rowWidth = Math.max(20, listWidth - AGE_WIDTH - 1)
|
||||
const previewWidth = previewOnRight
|
||||
? Math.max(20, columns - listWidth - 12)
|
||||
: Math.max(20, columns - 10)
|
||||
|
||||
return (
|
||||
<FuzzyPicker
|
||||
title="Search prompts"
|
||||
placeholder="Filter history…"
|
||||
initialQuery={initialQuery}
|
||||
items={filtered}
|
||||
getKey={item => String(item.entry.timestamp)}
|
||||
onQueryChange={setQuery}
|
||||
onSelect={item => {
|
||||
logEvent('tengu_history_picker_select', {
|
||||
result_count: filtered.length,
|
||||
query_length: query.length,
|
||||
})
|
||||
void item.entry.resolve().then(onSelect)
|
||||
}}
|
||||
onCancel={onCancel}
|
||||
emptyMessage={q =>
|
||||
items === null
|
||||
? 'Loading…'
|
||||
: q
|
||||
? 'No matching prompts'
|
||||
: 'No history yet'
|
||||
}
|
||||
selectAction="use"
|
||||
direction="up"
|
||||
previewPosition={previewOnRight ? 'right' : 'bottom'}
|
||||
renderItem={(item, isFocused) => (
|
||||
<Text>
|
||||
<Text dimColor>{item.age}</Text>
|
||||
<Text color={isFocused ? 'suggestion' : undefined}>
|
||||
{' '}
|
||||
{truncateToWidth(item_2.firstLine, rowWidth)}
|
||||
{truncateToWidth(item.firstLine, rowWidth)}
|
||||
</Text>
|
||||
</Text>} renderPreview={item_3 => {
|
||||
const wrapped = wrapAnsi(item_3.display, previewWidth, {
|
||||
hard: true
|
||||
}).split('\n').filter(l => l.trim() !== '');
|
||||
const overflow = wrapped.length > PREVIEW_ROWS;
|
||||
const shown = wrapped.slice(0, overflow ? PREVIEW_ROWS - 1 : PREVIEW_ROWS);
|
||||
const more = wrapped.length - shown.length;
|
||||
return <Box flexDirection="column" borderStyle="round" borderDimColor paddingX={1} height={PREVIEW_ROWS + 2}>
|
||||
{shown.map((row, i) => <Text key={i} dimColor>
|
||||
</Text>
|
||||
)}
|
||||
renderPreview={item => {
|
||||
const wrapped = wrapAnsi(item.display, previewWidth, { hard: true })
|
||||
.split('\n')
|
||||
.filter(l => l.trim() !== '')
|
||||
const overflow = wrapped.length > PREVIEW_ROWS
|
||||
const shown = wrapped.slice(
|
||||
0,
|
||||
overflow ? PREVIEW_ROWS - 1 : PREVIEW_ROWS,
|
||||
)
|
||||
const more = wrapped.length - shown.length
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderDimColor
|
||||
paddingX={1}
|
||||
height={PREVIEW_ROWS + 2}
|
||||
>
|
||||
{shown.map((row, i) => (
|
||||
<Text key={i} dimColor>
|
||||
{row}
|
||||
</Text>)}
|
||||
</Text>
|
||||
))}
|
||||
{more > 0 && <Text dimColor>{`… +${more} more lines`}</Text>}
|
||||
</Box>;
|
||||
}} />;
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function isSubsequence(text: string, query: string): boolean {
|
||||
let j = 0;
|
||||
let j = 0
|
||||
for (let i = 0; i < text.length && j < query.length; i++) {
|
||||
if (text[i] === query[j]) j++;
|
||||
if (text[i] === query[j]) j++
|
||||
}
|
||||
return j === query.length;
|
||||
return j === query.length
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user