mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 16:55:51 +00:00
feat(effort): EffortPanel 集成波纹背景——cursor 停在 ultracode 时切换波纹模式
仅在 cursor === 'ultracode' 时启用 useRippleFrame,渲染 5 行波纹背景 + overlay 文字(Faster/Smarter、分隔线、▲、档位名、副标签)。 其余档位保持原 PlainContent 渲染路径不动。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -14,12 +14,28 @@ import {
|
|||||||
import { executeEffort } from '../../commands/effort/effort.js';
|
import { executeEffort } from '../../commands/effort/effort.js';
|
||||||
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
|
import { useMainLoopModel } from '../../hooks/useMainLoopModel.js';
|
||||||
import { useSetAppState } from '../../state/AppState.js';
|
import { useSetAppState } from '../../state/AppState.js';
|
||||||
|
import { useRippleFrame } from './useRippleFrame.js';
|
||||||
|
import { type Overlay, computeRippleLine, mergeLayers } from './rippleAnimation.js';
|
||||||
|
|
||||||
// 每档固定宽度,Ink Box 自动对齐。PANEL_WIDTH = SEGMENT * 6。
|
// 每档固定宽度,Ink Box 自动对齐。PANEL_WIDTH = SEGMENT * 6。
|
||||||
const SEGMENT = 12;
|
const SEGMENT = 12;
|
||||||
const PANEL_WIDTH = SEGMENT * PANEL_POSITIONS.length;
|
const PANEL_WIDTH = SEGMENT * PANEL_POSITIONS.length;
|
||||||
const SUBLABEL_ULTRACODE = 'xhigh + workflows';
|
const SUBLABEL_ULTRACODE = 'xhigh + workflows';
|
||||||
|
|
||||||
|
// 波纹震源坐标(相对波纹区域坐标系,y=0 是档位名行)。
|
||||||
|
// ultracode 字符在 SEGMENT*5=60 起始段内居中(9 字符 in 12 列 → 偏移 1.5 → 1),
|
||||||
|
// 中心列 ≈ 60 + 1 + 4 = 65。
|
||||||
|
const RIPPLE_SOURCE_X = SEGMENT * 5 + 5;
|
||||||
|
const RIPPLE_SOURCE_Y = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算某段 idx 内居中文字的起始列。
|
||||||
|
* 'ultracode' = 9 字符 in SEGMENT=12 → offset = floor((12-9)/2) = 1。
|
||||||
|
*/
|
||||||
|
function segmentTextStartX(idx: number, textLen: number): number {
|
||||||
|
return SEGMENT * idx + Math.max(0, Math.floor((SEGMENT - textLen) / 2));
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appStateEffort: EffortValue | undefined;
|
appStateEffort: EffortValue | undefined;
|
||||||
onDone: (message: string) => void;
|
onDone: (message: string) => void;
|
||||||
@@ -36,6 +52,9 @@ export function EffortPanel({ appStateEffort, onDone }: Props): React.ReactNode
|
|||||||
const [cursor, setCursor] = React.useState<PanelPosition>(initialCursor);
|
const [cursor, setCursor] = React.useState<PanelPosition>(initialCursor);
|
||||||
const [done, setDone] = React.useState(false);
|
const [done, setDone] = React.useState(false);
|
||||||
|
|
||||||
|
const rippleActive = cursor === 'ultracode';
|
||||||
|
const [rippleRef, time] = useRippleFrame(rippleActive);
|
||||||
|
|
||||||
const handleConfirm = React.useCallback(() => {
|
const handleConfirm = React.useCallback(() => {
|
||||||
if (done) return;
|
if (done) return;
|
||||||
setDone(true);
|
setDone(true);
|
||||||
@@ -70,19 +89,49 @@ export function EffortPanel({ appStateEffort, onDone }: Props): React.ReactNode
|
|||||||
const envActive = envOverride !== null && envOverride !== undefined;
|
const envActive = envOverride !== null && envOverride !== undefined;
|
||||||
const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL;
|
const envRaw = process.env.CLAUDE_CODE_EFFORT_LEVEL;
|
||||||
|
|
||||||
|
// 波纹行渲染:返回 merge 后的单字符串。
|
||||||
|
const renderRippleLine = React.useCallback(
|
||||||
|
(relY: number, overlays: Overlay[]): string => {
|
||||||
|
const ripple = computeRippleLine({
|
||||||
|
y: relY + RIPPLE_SOURCE_Y,
|
||||||
|
width: PANEL_WIDTH,
|
||||||
|
time,
|
||||||
|
sourceX: RIPPLE_SOURCE_X,
|
||||||
|
sourceY: RIPPLE_SOURCE_Y,
|
||||||
|
});
|
||||||
|
return mergeLayers(ripple, overlays);
|
||||||
|
},
|
||||||
|
[time],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" paddingX={1} width={PANEL_WIDTH + 2}>
|
<Box ref={rippleRef} flexDirection="column" paddingX={1} width={PANEL_WIDTH + 2}>
|
||||||
<Text bold color="suggestion">
|
<Text bold color="suggestion">
|
||||||
Effort
|
Effort
|
||||||
</Text>
|
</Text>
|
||||||
{envActive && <Text color="warning">{`⚠ CLAUDE_CODE_EFFORT_LEVEL=${envRaw} overrides this session`}</Text>}
|
{envActive && <Text color="warning">{`⚠ CLAUDE_CODE_EFFORT_LEVEL=${envRaw} overrides this session`}</Text>}
|
||||||
|
{rippleActive ? (
|
||||||
|
<RippleContent time={time} renderLine={renderRippleLine} cursor={cursor} />
|
||||||
|
) : (
|
||||||
|
<PlainContent cursor={cursor} />
|
||||||
|
)}
|
||||||
|
<Box marginTop={1}>
|
||||||
|
<Text color="subtle">←/→ adjust · Enter confirm · Esc cancel</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 普通模式(无波纹)----
|
||||||
|
|
||||||
|
function PlainContent({ cursor }: { cursor: PanelPosition }): React.ReactNode {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<Box marginTop={1} flexDirection="row" justifyContent="space-between">
|
<Box marginTop={1} flexDirection="row" justifyContent="space-between">
|
||||||
<Text color="suggestion">Faster</Text>
|
<Text color="suggestion">Faster</Text>
|
||||||
<Text color="suggestion">Smarter</Text>
|
<Text color="suggestion">Smarter</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{/* 分隔线 */}
|
|
||||||
<Text color="subtle">{'─'.repeat(PANEL_WIDTH)}</Text>
|
<Text color="subtle">{'─'.repeat(PANEL_WIDTH)}</Text>
|
||||||
{/* ▲ 行:每段独立居中,与下方档位文字严格对齐 */}
|
|
||||||
<Box flexDirection="row">
|
<Box flexDirection="row">
|
||||||
{PANEL_POSITIONS.map(p => (
|
{PANEL_POSITIONS.map(p => (
|
||||||
<Box key={`cursor-${p}`} width={SEGMENT} justifyContent="center">
|
<Box key={`cursor-${p}`} width={SEGMENT} justifyContent="center">
|
||||||
@@ -92,7 +141,6 @@ export function EffortPanel({ appStateEffort, onDone }: Props): React.ReactNode
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
{/* 档位名:选中 suggestion + bold,未选中 subtle */}
|
|
||||||
<Box flexDirection="row">
|
<Box flexDirection="row">
|
||||||
{PANEL_POSITIONS.map(p => (
|
{PANEL_POSITIONS.map(p => (
|
||||||
<Box key={`label-${p}`} width={SEGMENT} justifyContent="center">
|
<Box key={`label-${p}`} width={SEGMENT} justifyContent="center">
|
||||||
@@ -102,16 +150,78 @@ export function EffortPanel({ appStateEffort, onDone }: Props): React.ReactNode
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
{/* ultracode 副标签:右对齐到最末段 */}
|
|
||||||
<Box flexDirection="row">
|
<Box flexDirection="row">
|
||||||
<Box width={SEGMENT * (PANEL_POSITIONS.length - 1)} />
|
<Box width={SEGMENT * (PANEL_POSITIONS.length - 1)} />
|
||||||
<Box width={SEGMENT} justifyContent="center">
|
<Box width={SEGMENT} justifyContent="center">
|
||||||
<Text color="subtle">{SUBLABEL_ULTRACODE}</Text>
|
<Text color="subtle">{SUBLABEL_ULTRACODE}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box marginTop={1}>
|
</>
|
||||||
<Text color="subtle">←/→ adjust · Enter confirm · Esc cancel</Text>
|
);
|
||||||
</Box>
|
}
|
||||||
</Box>
|
|
||||||
|
// ---- 波纹模式(cursor === 'ultracode')----
|
||||||
|
|
||||||
|
type RippleContentProps = {
|
||||||
|
time: number;
|
||||||
|
renderLine: (relY: number, overlays: Overlay[]) => string;
|
||||||
|
cursor: PanelPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
function RippleContent({ renderLine }: RippleContentProps): React.ReactNode {
|
||||||
|
// 各档位名 overlay(基于段中心对齐)
|
||||||
|
const labelOverlays: Overlay[] = PANEL_POSITIONS.map((p, idx) => ({
|
||||||
|
text: p,
|
||||||
|
x: segmentTextStartX(idx, p.length),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ▲ overlay:放在 ultracode 段中心
|
||||||
|
const cursorIdx = PANEL_POSITIONS.indexOf('ultracode');
|
||||||
|
const cursorOverlay: Overlay = {
|
||||||
|
text: '▲',
|
||||||
|
x: segmentTextStartX(cursorIdx, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 副标签 overlay:放在 ultracode 段中心
|
||||||
|
const sublabelOverlay: Overlay = {
|
||||||
|
text: SUBLABEL_ULTRACODE,
|
||||||
|
x: segmentTextStartX(cursorIdx, SUBLABEL_ULTRACODE.length),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Faster / Smarter overlay
|
||||||
|
const fasterOverlay: Overlay = { text: 'Faster', x: 0 };
|
||||||
|
const smarterOverlay: Overlay = {
|
||||||
|
text: 'Smarter',
|
||||||
|
x: PANEL_WIDTH - 'Smarter'.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分隔线 overlay
|
||||||
|
const separatorOverlay: Overlay = {
|
||||||
|
text: '─'.repeat(PANEL_WIDTH),
|
||||||
|
x: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 各行 y 坐标(相对震源 RIPPLE_SOURCE_Y = 档位名行)
|
||||||
|
// y=-3: Faster/Smarter
|
||||||
|
// y=-2: 分隔线
|
||||||
|
// y=-1: ▲
|
||||||
|
// y=0: 档位名(震源)
|
||||||
|
// y=1: 副标签
|
||||||
|
const fasterLine = renderLine(-3, [fasterOverlay, smarterOverlay]);
|
||||||
|
const separatorLine = renderLine(-2, [separatorOverlay]);
|
||||||
|
const cursorLine = renderLine(-1, [cursorOverlay]);
|
||||||
|
const labelLine = renderLine(0, labelOverlays);
|
||||||
|
const sublabelLine = renderLine(1, [sublabelOverlay]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text color="subtle">{fasterLine}</Text>
|
||||||
|
<Text color="subtle">{separatorLine}</Text>
|
||||||
|
<Text color="subtle">{cursorLine}</Text>
|
||||||
|
<Text color="suggestion" bold>
|
||||||
|
{labelLine}
|
||||||
|
</Text>
|
||||||
|
<Text color="subtle">{sublabelLine}</Text>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user