docs(guide): rewrite CLAUDE.md with ZCLAW-first perspective

Major changes:
- Shift from "OpenFang desktop client" to "independent AI Agent desktop app"
- Add decision principle: "Is this useful for ZCLAW? Does it affect ZCLAW?"
- Simplify project structure and tech stack sections
- Replace OpenClaw vs OpenFang comparison with unified backend approach
- Consolidate troubleshooting from scattered sections into organized FAQ
- Update Hands system documentation with 8 capabilities and status
- Stream
This commit is contained in:
iven
2026-03-20 19:30:09 +08:00
parent 3518fc8ece
commit 6f72442531
63 changed files with 8920 additions and 857 deletions

View File

@@ -62,13 +62,13 @@ function validateParameter(param: HandParameter, value: unknown): ValidationResu
// Required check
if (param.required) {
if (value === undefined || value === null || value === '') {
return { isValid: false, error: `${param.label} is required` };
return { isValid: false, error: `${param.label} 为必填项` };
}
if (Array.isArray(value) && value.length === 0) {
return { isValid: false, error: `${param.label} is required` };
return { isValid: false, error: `${param.label} 为必填项` };
}
if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value as Record<string, unknown>).length === 0) {
return { isValid: false, error: `${param.label} is required` };
return { isValid: false, error: `${param.label} 为必填项` };
}
}
@@ -81,26 +81,26 @@ function validateParameter(param: HandParameter, value: unknown): ValidationResu
switch (param.type) {
case 'number':
if (typeof value !== 'number' || isNaN(value)) {
return { isValid: false, error: `${param.label} must be a valid number` };
return { isValid: false, error: `${param.label} 必须是有效数字` };
}
if (param.min !== undefined && value < param.min) {
return { isValid: false, error: `${param.label} must be at least ${param.min}` };
return { isValid: false, error: `${param.label} 不能小于 ${param.min}` };
}
if (param.max !== undefined && value > param.max) {
return { isValid: false, error: `${param.label} must be at most ${param.max}` };
return { isValid: false, error: `${param.label} 不能大于 ${param.max}` };
}
break;
case 'text':
case 'textarea':
if (typeof value !== 'string') {
return { isValid: false, error: `${param.label} must be text` };
return { isValid: false, error: `${param.label} 必须是文本` };
}
if (param.pattern) {
try {
const regex = new RegExp(param.pattern);
if (!regex.test(value)) {
return { isValid: false, error: `${param.label} format is invalid` };
return { isValid: false, error: `${param.label} 格式不正确` };
}
} catch {
// Invalid regex pattern, skip validation
@@ -110,19 +110,19 @@ function validateParameter(param: HandParameter, value: unknown): ValidationResu
case 'array':
if (!Array.isArray(value)) {
return { isValid: false, error: `${param.label} must be an array` };
return { isValid: false, error: `${param.label} 必须是数组` };
}
break;
case 'object':
if (typeof value !== 'object' || Array.isArray(value)) {
return { isValid: false, error: `${param.label} must be an object` };
return { isValid: false, error: `${param.label} 必须是对象` };
}
try {
// Try to stringify to validate JSON
JSON.stringify(value);
} catch {
return { isValid: false, error: `${param.label} contains invalid JSON` };
return { isValid: false, error: `${param.label} 包含无效的 JSON` };
}
break;
@@ -210,7 +210,7 @@ function TextParamInput({ param, value, onChange, disabled, error }: ParamInputP
onChange={(e) => onChange(e.target.value)}
placeholder={param.placeholder}
disabled={disabled}
className={`w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed ${
className={`w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:opacity-50 disabled:cursor-not-allowed ${
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
}`}
/>
@@ -230,7 +230,7 @@ function NumberParamInput({ param, value, onChange, disabled, error }: ParamInpu
min={param.min}
max={param.max}
disabled={disabled}
className={`w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed ${
className={`w-full px-3 py-2 text-sm border rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:opacity-50 disabled:cursor-not-allowed ${
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
}`}
/>
@@ -245,10 +245,10 @@ function BooleanParamInput({ param, value, onChange, disabled }: ParamInputProps
checked={(value as boolean) ?? false}
onChange={(e) => onChange(e.target.checked)}
disabled={disabled}
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
className="w-4 h-4 text-gray-600 border-gray-300 rounded focus:ring-gray-400 disabled:opacity-50 disabled:cursor-not-allowed"
/>
<span className="text-sm text-gray-600 dark:text-gray-400">
{param.placeholder || 'Enabled'}
{param.placeholder || '启用'}
</span>
</label>
);
@@ -264,7 +264,7 @@ function SelectParamInput({ param, value, onChange, disabled, error }: ParamInpu
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
}`}
>
<option value="">{param.placeholder || '-- Select --'}</option>
<option value="">{param.placeholder || '-- 请选择 --'}</option>
{param.options?.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
@@ -331,7 +331,7 @@ function ArrayParamInput({ param, value, onChange, disabled, error }: ParamInput
value={item}
onChange={(e) => handleUpdateItem(index, e.target.value)}
disabled={disabled}
className="flex-1 px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
className="flex-1 px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-gray-400 disabled:opacity-50"
/>
<button
type="button"
@@ -353,7 +353,7 @@ function ArrayParamInput({ param, value, onChange, disabled, error }: ParamInput
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={param.placeholder || 'Add item...'}
placeholder={param.placeholder || '添加项目...'}
disabled={disabled}
className="flex-1 px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
/>
@@ -361,7 +361,7 @@ function ArrayParamInput({ param, value, onChange, disabled, error }: ParamInput
type="button"
onClick={handleAddItem}
disabled={disabled || !newItem.trim()}
className="p-1 text-blue-500 hover:text-blue-700 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded disabled:opacity-50 disabled:cursor-not-allowed"
className="p-1 text-gray-500 hover:text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-900/20 rounded disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="w-4 h-4" />
</button>
@@ -408,10 +408,10 @@ function ObjectParamInput({ param, value, onChange, disabled, error }: ParamInpu
onChange(result.data as Record<string, unknown>);
setParseError(null);
} else {
setParseError('Value must be a JSON object');
setParseError('值必须是 JSON 对象');
}
} else {
setParseError('Invalid JSON format');
setParseError('JSON 格式无效');
}
};
@@ -423,7 +423,7 @@ function ObjectParamInput({ param, value, onChange, disabled, error }: ParamInpu
className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
>
{isExpanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
{isExpanded ? 'Collapse' : 'Expand'} JSON Editor
{isExpanded ? '收起' : '展开'} JSON
</button>
{isExpanded && (
@@ -603,7 +603,7 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
className="flex items-center gap-1.5 px-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
>
<Save className="w-3.5 h-3.5" />
Save Preset
</button>
<button
type="button"
@@ -612,7 +612,7 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
className="flex items-center gap-1.5 px-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
<FolderOpen className="w-3.5 h-3.5" />
Load Preset ({presets.length})
({presets.length})
</button>
</div>
@@ -620,15 +620,15 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
{showSaveDialog && (
<div className="mt-3 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Preset Name
</label>
<div className="flex items-center gap-2">
<input
type="text"
value={presetName}
onChange={(e) => setPresetName(e.target.value)}
placeholder="My preset..."
className="flex-1 px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="我的预设..."
className="flex-1 px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400"
onKeyDown={(e) => {
if (e.key === 'Enter') handleSavePreset();
if (e.key === 'Escape') setShowSaveDialog(false);
@@ -639,16 +639,16 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
type="button"
onClick={handleSavePreset}
disabled={!presetName.trim()}
className="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
className="px-3 py-1.5 text-sm bg-gray-700 dark:bg-gray-600 text-white rounded-md hover:bg-gray-800 dark:hover:bg-gray-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
Save
</button>
<button
type="button"
onClick={() => setShowSaveDialog(false)}
className="px-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
>
Cancel
</button>
</div>
</div>
@@ -658,7 +658,7 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
{showPresetList && presets.length > 0 && (
<div className="mt-3 p-3 bg-gray-50 dark:bg-gray-900 rounded-lg space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Available Presets
</label>
<div className="space-y-1.5 max-h-48 overflow-y-auto">
{presets.map((preset) => (
@@ -678,9 +678,9 @@ function PresetManager({ presetKey, currentValues, onLoadPreset }: PresetManager
<button
type="button"
onClick={() => handleLoadPreset(preset)}
className="px-2 py-1 text-xs text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded"
className="px-2 py-1 text-xs text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900/20 rounded"
>
Load
</button>
<button
type="button"
@@ -753,7 +753,7 @@ export function HandParamsForm({
if (parameters.length === 0) {
return (
<div className="text-center py-4 text-gray-500 dark:text-gray-400 text-sm">
No parameters required for this Hand.
</div>
);
}