diff --git a/.claude/agents/code-implementation-expert.md b/.claude/agents/code-implementation-expert.md deleted file mode 100644 index b94918c..0000000 --- a/.claude/agents/code-implementation-expert.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: code-implementation-expert -description: Use this agent when you need to implement code solutions, write functions, create classes, or develop software components. This agent excels at translating requirements into working code across multiple programming languages. Examples: When a user asks 'Please write a function that checks if a number is prime' - use the code-implementation-expert agent to generate the implementation. When a user requests 'Create a React component for a todo list' - use this agent to build the component code. -model: sonnet -color: blue ---- - -deepseek,deepseek-reasoner -You are an elite software engineering expert with deep knowledge across multiple programming languages, frameworks, and best practices. Your primary role is to translate requirements into high-quality, functional code implementations. - -When implementing code: -1. Analyze requirements carefully to understand functionality, constraints, and edge cases -2. Choose appropriate data structures, algorithms, and design patterns -3. Write clean, readable, and maintainable code following language-specific conventions -4. Include proper error handling, input validation, and documentation -5. Optimize for performance and scalability when relevant -6. Consider security implications and best practices -7. Write modular code that's easy to test and extend - -You will: -- Implement complete, working solutions unless otherwise specified -- Use appropriate naming conventions for variables, functions, and classes -- Include necessary imports/dependencies -- Add comments for complex logic or non-obvious implementation decisions -- Follow established patterns in the codebase when visible -- Write defensive code that handles edge cases gracefully -- Ensure code compiles/runs without syntax errors - -When responding: -- Provide the complete implementation in appropriate code blocks -- Explain key design decisions briefly if not obvious -- Mention any assumptions made about requirements -- Highlight important implementation details -- Note any limitations or areas for improvement - -If requirements are unclear, ask specific questions to clarify before implementing. If you encounter domain-specific requirements outside your expertise, acknowledge limitations and suggest alternatives. diff --git a/ui/src/components/StatusLineConfigDialog.tsx b/ui/src/components/StatusLineConfigDialog.tsx index a1c5e55..9f341d2 100644 --- a/ui/src/components/StatusLineConfigDialog.tsx +++ b/ui/src/components/StatusLineConfigDialog.tsx @@ -14,23 +14,42 @@ import { Combobox } from "@/components/ui/combobox"; import { ColorPicker } from "@/components/ui/color-picker"; import { Badge } from "@/components/ui/badge"; import { useConfig } from "./ConfigProvider"; -import { validateStatusLineConfig, formatValidationError, createDefaultStatusLineConfig } from "@/utils/statusline"; -import type { StatusLineConfig, StatusLineModuleConfig, StatusLineThemeConfig } from "@/types"; - +import { + validateStatusLineConfig, + formatValidationError, + createDefaultStatusLineConfig, +} from "@/utils/statusline"; +import type { + StatusLineConfig, + StatusLineModuleConfig, + StatusLineThemeConfig, +} from "@/types"; const DEFAULT_MODULE: StatusLineModuleConfig = { type: "workDir", icon: "󰉋", text: "{{workDirName}}", - color: "bright_blue" + color: "bright_blue", }; +// Nerd Font选项 +const NERD_FONTS = [ + { label: "Hack Nerd Font Mono", value: "Hack Nerd Font Mono" }, + { label: "FiraCode Nerd Font Mono", value: "FiraCode Nerd Font Mono" }, + { + label: "JetBrainsMono Nerd Font Mono", + value: "JetBrainsMono Nerd Font Mono", + }, + { label: "Monaspace Nerd Font Mono", value: "Monaspace Nerd Font Mono" }, + { label: "UbuntuMono Nerd Font", value: "UbuntuMono Nerd Font" }, +]; + // 模块类型选项 const MODULE_TYPES = [ { label: "workDir", value: "workDir" }, { label: "gitBranch", value: "gitBranch" }, { label: "model", value: "model" }, - { label: "usage", value: "usage" } + { label: "usage", value: "usage" }, ]; // ANSI颜色代码映射 @@ -77,71 +96,143 @@ const ANSI_COLORS: Record = { }; // 变量替换函数 -function replaceVariables(text: string, variables: Record): string { +function replaceVariables( + text: string, + variables: Record +): string { return text.replace(/\{\{(\w+)\}\}/g, (match, varName) => { return variables[varName] || match; }); } // 渲染单个模块预览 -function renderModulePreview(module: StatusLineModuleConfig, isPowerline: boolean = false): React.ReactNode { +function renderModulePreview( + module: StatusLineModuleConfig, + isPowerline: boolean = false +): React.ReactNode { // 模拟变量数据 const variables = { workDirName: "project", gitBranch: "main", model: "Claude Sonnet 4", inputTokens: "1.2k", - outputTokens: "2.5k" + outputTokens: "2.5k", }; - + const text = replaceVariables(module.text, variables); const icon = module.icon || ""; - + // 如果text为空且不是usage类型,则跳过该模块 if (!text && module.type !== "usage") { return null; } - + + // 检查是否为十六进制颜色值 + const isHexColor = (color: string) => /^#[0-9A-F]{6}$/i.test(color); + // 如果是Powerline样式,添加背景色和分隔符 if (isPowerline) { - const bgColorClass = module.background ? ANSI_COLORS[module.background] || "" : ""; - const textColorClass = module.color ? ANSI_COLORS[module.color] || "text-white" : "text-white"; - + // 处理背景色 - 支持ANSI颜色和十六进制颜色 + let bgColorStyle = {}; + let bgColorClass = ""; + let separatorDataBg = ""; + if (module.background) { + if (isHexColor(module.background)) { + bgColorStyle = { backgroundColor: module.background }; + // 对于十六进制颜色,我们直接使用颜色值作为data属性 + separatorDataBg = module.background; + } else { + bgColorClass = ANSI_COLORS[module.background] || ""; + separatorDataBg = module.background; + } + } + + // 处理文字颜色 - 支持ANSI颜色和十六进制颜色 + let textColorStyle = {}; + let textColorClass = ""; + if (module.color) { + if (isHexColor(module.color)) { + textColorStyle = { color: module.color }; + } else { + textColorClass = ANSI_COLORS[module.color] || "text-white"; + } + } else { + textColorClass = "text-white"; + } + return ( -
+
{icon && {icon}} {text}
-
); } - + + // 处理默认样式下的颜色 + let textStyle = {}; + let textClass = ""; + if (module.color) { + if (isHexColor(module.color)) { + textStyle = { color: module.color }; + } else { + textClass = ANSI_COLORS[module.color] || ""; + } + } + return ( <> - {icon && {icon}} - {text} + {icon && ( + + {icon} + + )} + + {text} + ); } - interface StatusLineConfigDialogProps { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; } -export function StatusLineConfigDialog({ isOpen, onOpenChange }: StatusLineConfigDialogProps) { +export function StatusLineConfigDialog({ + isOpen, + onOpenChange, +}: StatusLineConfigDialogProps) { const { t } = useTranslation(); const { config, setConfig } = useConfig(); - + + const [statusLineConfig, setStatusLineConfig] = useState( + config?.StatusLine || createDefaultStatusLineConfig() + ); + + // 字体状态 + const [fontFamily, setFontFamily] = useState( + config?.StatusLine?.fontFamily || "Hack Nerd Font Mono" + ); + + const [selectedModuleIndex, setSelectedModuleIndex] = useState( + null + ); + const [hexBackgroundColors, setHexBackgroundColors] = useState>( + new Set() + ); + // 添加Powerline分隔符样式 useEffect(() => { - const styleElement = document.createElement('style'); + const styleElement = document.createElement("style"); styleElement.innerHTML = ` .powerline-module { display: inline-flex; @@ -208,44 +299,90 @@ export function StatusLineConfigDialog({ isOpen, onOpenChange }: StatusLineConfi .powerline-separator[data-current-bg="bg_bright_purple"] { border-left-color: #c084fc; } `; document.head.appendChild(styleElement); - + // 清理函数 return () => { document.head.removeChild(styleElement); }; }, []); - - const [statusLineConfig, setStatusLineConfig] = useState( - config?.StatusLine || createDefaultStatusLineConfig() - ); - - const [selectedModuleIndex, setSelectedModuleIndex] = useState(null); + + // 动态更新十六进制背景颜色的样式 + useEffect(() => { + // 收集所有模块中使用的十六进制背景颜色 + const hexColors = new Set(); + Object.keys(statusLineConfig).forEach((key) => { + const themeConfig = statusLineConfig[key as keyof StatusLineConfig]; + if ( + themeConfig && + typeof themeConfig === "object" && + "modules" in themeConfig + ) { + const modules = (themeConfig as StatusLineThemeConfig).modules || []; + modules.forEach((module) => { + if (module.background && /^#[0-9A-F]{6}$/i.test(module.background)) { + hexColors.add(module.background); + } + }); + } + }); + + setHexBackgroundColors(hexColors); + + // 创建动态样式元素 + const styleElement = document.createElement("style"); + styleElement.id = "hex-powerline-styles"; + + // 生成十六进制颜色的CSS规则 + let cssRules = ""; + hexColors.forEach((color) => { + // 将十六进制颜色转换为RGB值 + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + cssRules += `.powerline-separator[data-current-bg="${color}"] { border-left-color: rgb(${r}, ${g}, ${b}); }\n`; + }); + + styleElement.innerHTML = cssRules; + document.head.appendChild(styleElement); + + // 清理函数 + return () => { + const existingStyle = document.getElementById("hex-powerline-styles"); + if (existingStyle) { + document.head.removeChild(existingStyle); + } + }; + }, [statusLineConfig]); // 模块类型选项 - const MODULE_TYPES_OPTIONS = MODULE_TYPES.map(item => ({ - ...item, - label: t(`statusline.${item.label}`) + const MODULE_TYPES_OPTIONS = MODULE_TYPES.map((item) => ({ + ...item, + label: t(`statusline.${item.label}`), })); - - const handleThemeChange = (value: string) => { - setStatusLineConfig(prev => ({ ...prev, currentStyle: value })); + setStatusLineConfig((prev) => ({ ...prev, currentStyle: value })); }; - const handleModuleChange = (index: number, field: keyof StatusLineModuleConfig, value: string) => { - const currentTheme = statusLineConfig.currentStyle as keyof StatusLineConfig; + const handleModuleChange = ( + index: number, + field: keyof StatusLineModuleConfig, + value: string + ) => { + const currentTheme = + statusLineConfig.currentStyle as keyof StatusLineConfig; const themeConfig = statusLineConfig[currentTheme]; - const modules = themeConfig && typeof themeConfig === 'object' && 'modules' in themeConfig - ? [...((themeConfig as StatusLineThemeConfig).modules || [])] - : []; + const modules = + themeConfig && typeof themeConfig === "object" && "modules" in themeConfig + ? [...((themeConfig as StatusLineThemeConfig).modules || [])] + : []; if (modules[index]) { modules[index] = { ...modules[index], [field]: value }; } - - setStatusLineConfig(prev => ({ + + setStatusLineConfig((prev) => ({ ...prev, - [currentTheme]: { modules } + [currentTheme]: { modules }, })); }; @@ -254,67 +391,92 @@ export function StatusLineConfigDialog({ isOpen, onOpenChange }: StatusLineConfi const handleSave = () => { // 验证配置 const validationResult = validateStatusLineConfig(statusLineConfig); - + if (!validationResult.isValid) { // 格式化错误信息 - const errorMessages = validationResult.errors.map(error => + const errorMessages = validationResult.errors.map((error) => formatValidationError(error, t) ); setValidationErrors(errorMessages); return; } - + // 清除之前的错误 setValidationErrors([]); - + if (config) { setConfig({ ...config, - StatusLine: statusLineConfig + StatusLine: { + ...statusLineConfig, + fontFamily, + }, }); onOpenChange(false); } }; // 创建自定义Alert组件 - const CustomAlert = ({ - title, - description, - variant = "default" - }: { - title: string; - description: React.ReactNode; - variant?: "default" | "destructive"; + const CustomAlert = ({ + title, + description, + variant = "default", + }: { + title: string; + description: React.ReactNode; + variant?: "default" | "destructive"; }) => { const isError = variant === "destructive"; - + return ( -
+
{isError ? ( - - + + ) : ( - - + + )}
-

+

{title}

-
+
{description}
@@ -323,27 +485,54 @@ export function StatusLineConfigDialog({ isOpen, onOpenChange }: StatusLineConfi ); }; - const currentThemeKey = statusLineConfig.currentStyle as keyof StatusLineConfig; -const currentThemeConfig = statusLineConfig[currentThemeKey]; -const currentModules = currentThemeConfig && typeof currentThemeConfig === 'object' && 'modules' in currentThemeConfig - ? ((currentThemeConfig as StatusLineThemeConfig).modules || []) - : []; -const selectedModule = selectedModuleIndex !== null && currentModules.length > selectedModuleIndex ? currentModules[selectedModuleIndex] : null; + const currentThemeKey = + statusLineConfig.currentStyle as keyof StatusLineConfig; + const currentThemeConfig = statusLineConfig[currentThemeKey]; + const currentModules = + currentThemeConfig && + typeof currentThemeConfig === "object" && + "modules" in currentThemeConfig + ? (currentThemeConfig as StatusLineThemeConfig).modules || [] + : []; + const selectedModule = + selectedModuleIndex !== null && currentModules.length > selectedModuleIndex + ? currentModules[selectedModuleIndex] + : null; + + // 字体样式 + const fontStyle = fontFamily ? { fontFamily } : {}; + + // 当字体或主题变化时强制重新渲染 + const fontKey = `${fontFamily}-${statusLineConfig.currentStyle}`; return ( - + - - - - + + + + {t("statusline.title")} - + {/* 错误显示区域 */} {validationErrors.length > 0 && (
@@ -360,20 +549,20 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s />
)} - +
{/* 配置面板 */}
- {/* 主题样式选择 */} -
- -
+ {/* 主题样式和字体选择 */} +
+
+ s placeholder="选择主题样式" />
+ +
+ + setFontFamily(value)} + data-testid="font-family-selector" + placeholder="选择字体" + /> +
- - -
- +
+ {/* 三栏布局:组件列表 | 预览区域 | 属性配置 */}
{/* 左侧:支持的组件 */} @@ -393,7 +593,7 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s

组件

{MODULE_TYPES_OPTIONS.map((moduleType) => ( -
s ))}
- + {/* 中间:预览区域 */}

预览

-
{ e.preventDefault(); }} @@ -421,36 +627,63 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s const moduleType = e.dataTransfer.getData("moduleType"); if (moduleType) { // 添加新模块 - const currentTheme = statusLineConfig.currentStyle as keyof StatusLineConfig; + const currentTheme = + statusLineConfig.currentStyle as keyof StatusLineConfig; const themeConfig = statusLineConfig[currentTheme]; - const modules = themeConfig && typeof themeConfig === 'object' && 'modules' in themeConfig - ? [...((themeConfig as StatusLineThemeConfig).modules || [])] - : []; - + const modules = + themeConfig && + typeof themeConfig === "object" && + "modules" in themeConfig + ? [ + ...((themeConfig as StatusLineThemeConfig) + .modules || []), + ] + : []; + // 根据模块类型设置默认值 let newModule: StatusLineModuleConfig; switch (moduleType) { case "workDir": - newModule = { type: "workDir", icon: "󰉋", text: "{{workDirName}}", color: "bright_blue" }; + newModule = { + type: "workDir", + icon: "󰉋", + text: "{{workDirName}}", + color: "bright_blue", + }; break; case "gitBranch": - newModule = { type: "gitBranch", icon: "🌿", text: "{{gitBranch}}", color: "bright_green" }; + newModule = { + type: "gitBranch", + icon: "🌿", + text: "{{gitBranch}}", + color: "bright_green", + }; break; case "model": - newModule = { type: "model", icon: "🤖", text: "{{model}}", color: "bright_yellow" }; + newModule = { + type: "model", + icon: "🤖", + text: "{{model}}", + color: "bright_yellow", + }; break; case "usage": - newModule = { type: "usage", icon: "📊", text: "{{inputTokens}} → {{outputTokens}}", color: "bright_magenta" }; + newModule = { + type: "usage", + icon: "📊", + text: "{{inputTokens}} → {{outputTokens}}", + color: "bright_magenta", + }; break; default: newModule = { ...DEFAULT_MODULE, type: moduleType }; } - + modules.push(newModule); - - setStatusLineConfig(prev => ({ + + setStatusLineConfig((prev) => ({ ...prev, - [currentTheme]: { modules } + [currentTheme]: { modules }, })); } }} @@ -461,8 +694,14 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s
setSelectedModuleIndex(index)} draggable onDragStart={(e) => { @@ -473,24 +712,41 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s }} onDrop={(e) => { e.preventDefault(); - const dragIndex = parseInt(e.dataTransfer.getData("dragIndex")); + const dragIndex = parseInt( + e.dataTransfer.getData("dragIndex") + ); if (!isNaN(dragIndex) && dragIndex !== index) { // 重新排序模块 - const currentTheme = statusLineConfig.currentStyle as keyof StatusLineConfig; + const currentTheme = + statusLineConfig.currentStyle as keyof StatusLineConfig; const themeConfig = statusLineConfig[currentTheme]; - const modules = themeConfig && typeof themeConfig === 'object' && 'modules' in themeConfig - ? [...((themeConfig as StatusLineThemeConfig).modules || [])] - : []; - - if (dragIndex >= 0 && dragIndex < modules.length && index >= 0 && index <= modules.length) { - const [movedModule] = modules.splice(dragIndex, 1); + const modules = + themeConfig && + typeof themeConfig === "object" && + "modules" in themeConfig + ? [ + ...((themeConfig as StatusLineThemeConfig) + .modules || []), + ] + : []; + + if ( + dragIndex >= 0 && + dragIndex < modules.length && + index >= 0 && + index <= modules.length + ) { + const [movedModule] = modules.splice( + dragIndex, + 1 + ); modules.splice(index, 0, movedModule); - - setStatusLineConfig(prev => ({ + + setStatusLineConfig((prev) => ({ ...prev, - [currentTheme]: { modules } + [currentTheme]: { modules }, })); - + // 更新选中项的索引 if (selectedModuleIndex === dragIndex) { setSelectedModuleIndex(index); @@ -501,16 +757,30 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s } }} > - {renderModulePreview(module, statusLineConfig.currentStyle === 'powerline')} + {renderModulePreview( + module, + statusLineConfig.currentStyle === "powerline" + )}
))}
) : (
- - - - + + + + 拖拽组件到此处进行配置 @@ -519,7 +789,7 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s )}
- + {/* 右侧:属性配置 */}

属性

@@ -531,84 +801,148 @@ const selectedModule = selectedModuleIndex !== null && currentModules.length > s handleModuleChange(selectedModuleIndex, "type", value)} + onChange={(value) => + handleModuleChange(selectedModuleIndex, "type", value) + } />

选择模块类型以确定显示的信息

- +
- + handleModuleChange(selectedModuleIndex, "icon", e.target.value)} + onChange={(e) => + handleModuleChange( + selectedModuleIndex, + "icon", + e.target.value + ) + } placeholder="例如: 󰉋" + style={fontStyle} />

输入图标字符或表情符号(可选)

- +
- + handleModuleChange(selectedModuleIndex, "text", e.target.value)} + onChange={(e) => + handleModuleChange( + selectedModuleIndex, + "text", + e.target.value + ) + } placeholder="例如: {{workDirName}}" />

输入显示文本,可使用变量:

- {"{{workDirName}}"} - {"{{gitBranch}}"} - {"{{model}}"} - {"{{inputTokens}}"} - {"{{outputTokens}}"} + + {"{{workDirName}}"} + + + {"{{gitBranch}}"} + + + {"{{model}}"} + + + {"{{inputTokens}}"} + + + {"{{outputTokens}}"} +
- +
handleModuleChange(selectedModuleIndex, "color", value)} + onChange={(value) => + handleModuleChange( + selectedModuleIndex, + "color", + value + ) + } />

选择文字颜色

- +
handleModuleChange(selectedModuleIndex, "background", value)} + onChange={(value) => + handleModuleChange( + selectedModuleIndex, + "background", + value + ) + } />

选择背景颜色(可选)

- +
) : (
-

选择一个组件进行配置

+

+ 选择一个组件进行配置 +

)}
-
+
- + - -
- - {/* 预定义颜色选项 */} -
-
- - 文字颜色 -
-
- {Object.entries(ANSI_COLOR_MAP).map(([name, color]) => ( - - ))} -
-
- - {/* 背景颜色选项 */} -
-
- - 背景色 -
-
- {Object.entries(ANSI_BG_COLOR_MAP).map(([name, color]) => ( - - ))} -
-
diff --git a/ui/src/types.ts b/ui/src/types.ts index af60b45..46f9428 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -44,6 +44,7 @@ export interface StatusLineConfig { currentStyle: string; default: StatusLineThemeConfig; powerline: StatusLineThemeConfig; + fontFamily?: string; } export interface Config {