Files
NetMesh/components/ai/ComposerThinkingChip.tsx
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

105 lines
3.6 KiB
TypeScript

import { Brain, Check, ChevronDown } from 'lucide-react';
import React, { useRef } from 'react';
import { createPortal } from 'react-dom';
import { useI18n } from '../../application/i18n/I18nProvider';
import { formatComposerThinkingLabel } from '../../infrastructure/ai/composerPicker';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
export interface ComposerThinkingChipProps {
levels: readonly string[];
selectedLevel?: string;
disabled?: boolean;
open: boolean;
menuPos: { left: number; bottom: number } | null;
onToggle: (rect: DOMRect | undefined) => void;
onSelect: (level: string) => void;
onClose: () => void;
}
const chipClassName =
'inline-flex h-6 items-center gap-1 rounded-full px-1.5 text-[10.5px] text-foreground/72';
export const ComposerThinkingChip: React.FC<ComposerThinkingChipProps> = ({
levels,
selectedLevel,
disabled = false,
open,
menuPos,
onToggle,
onSelect,
onClose,
}) => {
const { t } = useI18n();
const btnRef = useRef<HTMLButtonElement>(null);
const formatLevel = (level: string) => (
level === 'off' ? t('ai.chat.thinkingOff') : formatComposerThinkingLabel(level)
);
const label = selectedLevel
? formatLevel(selectedLevel)
: t('ai.chat.thinkingLevel');
return (
<>
<Tooltip>
<TooltipTrigger asChild>
<button
ref={btnRef}
type="button"
disabled={disabled}
onClick={() => onToggle(btnRef.current?.getBoundingClientRect())}
className={`${chipClassName} shrink-0 ${
disabled
? 'opacity-60'
: 'cursor-pointer hover:bg-muted/24 transition-colors'
}`}
aria-label={t('ai.chat.thinkingLevel')}
aria-expanded={open}
>
<Brain size={11} className="text-violet-400/75" />
<span className="truncate max-w-[56px]">{label}</span>
{!disabled && <ChevronDown size={9} className="text-muted-foreground/50" />}
</button>
</TooltipTrigger>
<TooltipContent>{t('ai.chat.thinkingLevel')}</TooltipContent>
</Tooltip>
{open && menuPos && createPortal(
<>
<div className="fixed inset-0 z-[999]" onClick={onClose} />
<div
role="listbox"
aria-label={t('ai.chat.thinkingLevel')}
className="fixed z-[1000] min-w-[148px] rounded-lg border border-border/50 bg-popover shadow-lg py-1"
style={{ left: menuPos.left, bottom: menuPos.bottom }}
>
{levels.map((level) => {
const isSelected = selectedLevel === level;
return (
<button
key={level}
type="button"
role="option"
aria-selected={isSelected}
onClick={() => {
if (!isSelected) onSelect(level);
else onClose();
}}
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-left text-[12px] hover:bg-muted/30 transition-colors cursor-pointer"
>
{isSelected
? <Check size={11} className="text-primary shrink-0" />
: <span className="w-[11px] shrink-0" />}
<Brain size={12} className="text-violet-400/70 shrink-0" />
<span className="text-foreground/85">{formatLevel(level)}</span>
</button>
);
})}
</div>
</>,
document.body,
)}
</>
);
};
export default React.memo(ComposerThinkingChip);