All files / src/components/Settings Workspace.tsx

0% Statements 0/85
0% Branches 0/1
0% Functions 0/1
0% Lines 0/85

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                                                                                                                                                                   
import { useEffect, useState } from 'react';
import { useConfigStore } from '../../store/configStore';
import { silentErrorHandler } from '../../lib/error-utils';
 
export function Workspace() {
  const quickConfig = useConfigStore((s) => s.quickConfig);
  const workspaceInfo = useConfigStore((s) => s.workspaceInfo);
  const loadWorkspaceInfo = useConfigStore((s) => s.loadWorkspaceInfo);
  const saveQuickConfig = useConfigStore((s) => s.saveQuickConfig);
  const [projectDir, setProjectDir] = useState('~/.openfang/zclaw-workspace');
 
  useEffect(() => {
    loadWorkspaceInfo().catch(silentErrorHandler('Workspace'));
  }, []);
 
  useEffect(() => {
    setProjectDir(quickConfig.workspaceDir || workspaceInfo?.path || '~/.openfang/zclaw-workspace');
  }, [quickConfig.workspaceDir, workspaceInfo?.path]);
 
  const handleWorkspaceBlur = async () => {
    const nextValue = projectDir.trim() || '~/.openfang/zclaw-workspace';
    setProjectDir(nextValue);
    await saveQuickConfig({ workspaceDir: nextValue });
    await loadWorkspaceInfo();
  };
 
  const handleToggle = async (
    key: 'restrictFiles' | 'autoSaveContext' | 'fileWatching',
    value: boolean
  ) => {
    await saveQuickConfig({ [key]: value });
  };
 
  const restrictFiles = quickConfig.restrictFiles ?? true;
  const autoSaveContext = quickConfig.autoSaveContext ?? true;
  const fileWatching = quickConfig.fileWatching ?? true;
 
  return (
    <div className="max-w-3xl">
      <h1 className="text-xl font-bold mb-2 text-gray-900">工作区</h1>
      <div className="text-xs text-gray-500 mb-6">配置本地项目目录与上下文持久化行为。</div>
 
      <div className="bg-white rounded-xl border border-gray-200 p-6 mb-6 shadow-sm">
        <label className="block text-sm font-medium text-gray-700 mb-2">默认项目目录</label>
        <div className="text-xs text-gray-500 mb-3">ZCLAW 项目和上下文文件的保存位置。</div>
        <div className="flex gap-2">
          <input
            type="text"
            value={projectDir}
            onChange={(e) => setProjectDir(e.target.value)}
            onBlur={() => { handleWorkspaceBlur().catch(silentErrorHandler('Workspace')); }}
            className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm bg-gray-50 focus:outline-none"
          />
          <button className="text-xs px-4 py-2 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
            浏览
          </button>
        </div>
        <div className="mt-3 space-y-1 text-xs text-gray-500">
          <div>当前解析路径:{workspaceInfo?.resolvedPath || '未解析'}</div>
          <div>文件数:{workspaceInfo?.fileCount ?? 0},大小:{workspaceInfo?.totalSize ?? 0} bytes</div>
        </div>
      </div>
 
      <div className="bg-white rounded-xl border border-gray-200 p-6 space-y-6 shadow-sm">
        <div className="flex justify-between items-start">
          <div className="flex-1 pr-4">
            <div className="font-medium text-gray-900 mb-1">限制文件访问范围</div>
            <div className="text-xs text-gray-500 leading-relaxed">
              开启后,Agent 的工作空间将限制在工作目录内。关闭后可访问更大范围,可能导致误操作。无论开关状态,均建议提前备份重要文件。请注意:受技术限制,我们无法保证完全阻止目录外执行或由此带来的外部影响;请自行评估风险并谨慎使用。
            </div>
          </div>
          <Toggle checked={restrictFiles} onChange={(value) => { handleToggle('restrictFiles', value).catch(silentErrorHandler('Workspace')); }} />
        </div>
 
        <div className="flex justify-between items-center py-3 border-t border-gray-100">
          <div>
            <div className="font-medium text-gray-900 mb-1">自动保存上下文</div>
            <div className="text-xs text-gray-500">自动将聊天记录和提取的产物保存到本地工作区文件夹。</div>
          </div>
          <Toggle checked={autoSaveContext} onChange={(value) => { handleToggle('autoSaveContext', value).catch(silentErrorHandler('Workspace')); }} />
        </div>
 
        <div className="flex justify-between items-center py-3 border-t border-gray-100">
          <div>
            <div className="font-medium text-gray-900 mb-1">文件监听</div>
            <div className="text-xs text-gray-500">监听本地文件变更,实时更新 Agent 上下文。</div>
          </div>
          <Toggle checked={fileWatching} onChange={(value) => { handleToggle('fileWatching', value).catch(silentErrorHandler('Workspace')); }} />
        </div>
 
      </div>
    </div>
  );
}
 
function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
  return (
    <button
      onClick={() => onChange(!checked)}
      className={`w-11 h-6 rounded-full transition-colors relative flex-shrink-0 mt-1 ${checked ? 'bg-orange-500' : 'bg-gray-300'}`}
    >
      <span className={`block w-5 h-5 bg-white rounded-full shadow-sm absolute top-0.5 transition-all ${checked ? 'left-5' : 'left-0.5'}`} />
    </button>
  );
}