This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { AgentsMdPanel } from '../userfolder/AgentsMdPanel';
|
||||
import { MemoryPanel } from '../userfolder/MemoryPanel';
|
||||
@@ -20,6 +21,7 @@ import { McpPanel } from '../userfolder/McpPanel';
|
||||
import { SshConnectionsPanel } from '../userfolder/SshConnectionsPanel';
|
||||
import { SpaceMembersPanel } from './SpaceMembersPanel';
|
||||
import { SpaceBrowserPanel } from './SpaceBrowserPanel';
|
||||
import { SpaceToolSettings } from './SpaceToolSettings';
|
||||
import { PieceEditor } from '../settings/PieceEditor';
|
||||
import { usePieceList } from '../../hooks/usePieces';
|
||||
import { splitPieces } from '../../lib/splitPieces';
|
||||
@@ -28,27 +30,29 @@ import { useAuthState } from '../../App';
|
||||
|
||||
type ShowToast = (message: string, variant?: 'success' | 'error') => void;
|
||||
|
||||
type SettingsSection = 'agents' | 'memory' | 'pieces' | 'skills' | 'mcp' | 'ssh' | 'browser' | 'members';
|
||||
type SettingsSection = 'agents' | 'memory' | 'pieces' | 'skills' | 'mcp' | 'ssh' | 'browser' | 'members' | 'tools';
|
||||
|
||||
const SECTIONS: { id: SettingsSection; label: string; testid: string }[] = [
|
||||
{ id: 'agents', label: 'AGENTS.md', testid: 'space-settings-nav-agents' },
|
||||
{ id: 'memory', label: 'メモリ', testid: 'space-settings-nav-memory' },
|
||||
{ id: 'pieces', label: 'Pieces', testid: 'space-settings-nav-pieces' },
|
||||
{ id: 'skills', label: 'スキル', testid: 'space-settings-nav-skills' },
|
||||
{ id: 'mcp', label: 'MCP', testid: 'space-settings-nav-mcp' },
|
||||
{ id: 'ssh', label: 'SSH', testid: 'space-settings-nav-ssh' },
|
||||
{ id: 'browser', label: 'ブラウザ', testid: 'space-settings-nav-browser' },
|
||||
{ id: 'members', label: 'メンバー', testid: 'space-settings-nav-members' },
|
||||
const SECTIONS: { id: SettingsSection; labelKey: string; testid: string }[] = [
|
||||
{ id: 'agents', labelKey: 'settings.nav.agents', testid: 'space-settings-nav-agents' },
|
||||
{ id: 'memory', labelKey: 'settings.nav.memory', testid: 'space-settings-nav-memory' },
|
||||
{ id: 'pieces', labelKey: 'settings.nav.pieces', testid: 'space-settings-nav-pieces' },
|
||||
{ id: 'skills', labelKey: 'settings.nav.skills', testid: 'space-settings-nav-skills' },
|
||||
{ id: 'mcp', labelKey: 'settings.nav.mcp', testid: 'space-settings-nav-mcp' },
|
||||
{ id: 'ssh', labelKey: 'settings.nav.ssh', testid: 'space-settings-nav-ssh' },
|
||||
{ id: 'browser', labelKey: 'settings.nav.browser', testid: 'space-settings-nav-browser' },
|
||||
{ id: 'tools', labelKey: 'settings.nav.tools', testid: 'space-settings-nav-tools' },
|
||||
{ id: 'members', labelKey: 'settings.nav.members', testid: 'space-settings-nav-members' },
|
||||
];
|
||||
|
||||
export function SpaceSettings({ spaceId, showToast }: { spaceId: string; showToast?: ShowToast }) {
|
||||
const { t } = useTranslation('spaces');
|
||||
const [section, setSection] = useState<SettingsSection>('agents');
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col md:flex-row md:gap-3">
|
||||
{/* Sub-nav: モバイルは横スクロールのセグメント、md+ は左の縦リスト。 */}
|
||||
<nav
|
||||
aria-label="ワークスペース設定"
|
||||
aria-label={t('settings.navLabel')}
|
||||
className="flex shrink-0 gap-1 overflow-x-auto border-b border-hairline pb-2 md:w-44 md:flex-col md:overflow-x-visible md:border-b-0 md:border-r md:pb-0 md:pr-3"
|
||||
>
|
||||
{SECTIONS.map(s => {
|
||||
@@ -65,7 +69,7 @@ export function SpaceSettings({ spaceId, showToast }: { spaceId: string; showToa
|
||||
: 'text-slate-600 hover:bg-surface hover:text-slate-900'
|
||||
}`}
|
||||
>
|
||||
{s.label}
|
||||
{t(s.labelKey)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -80,6 +84,7 @@ export function SpaceSettings({ spaceId, showToast }: { spaceId: string; showToa
|
||||
{section === 'mcp' && <McpPanel spaceId={spaceId} showToast={showToast} />}
|
||||
{section === 'ssh' && <SshConnectionsPanel spaceId={spaceId} showToast={showToast} />}
|
||||
{section === 'browser' && <SpaceBrowserPanel spaceId={spaceId} showToast={showToast} />}
|
||||
{section === 'tools' && <SpaceToolSettings spaceId={spaceId} showToast={showToast} />}
|
||||
{section === 'members' && <SpaceMembersPanel spaceId={spaceId} showToast={showToast} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,6 +97,7 @@ export function SpaceSettings({ spaceId, showToast }: { spaceId: string; showToa
|
||||
* `PieceEditor` の再利用で構成する。選択はローカル state。
|
||||
*/
|
||||
function SpacePiecesPanel({ spaceId, showToast }: { spaceId: string; showToast?: ShowToast }) {
|
||||
const { t } = useTranslation('spaces');
|
||||
const auth = useAuthState();
|
||||
const isAdmin = auth.mode === 'authenticated' && auth.user.role === 'admin';
|
||||
const qc = useQueryClient();
|
||||
@@ -129,7 +135,7 @@ function SpacePiecesPanel({ spaceId, showToast }: { spaceId: string; showToast?:
|
||||
setNewName('');
|
||||
setSelected({ name, source });
|
||||
} catch (e) {
|
||||
const msg = `Piece の作成に失敗しました: ${e instanceof Error ? e.message : String(e)}`;
|
||||
const msg = t('settings.pieces.createFailed', { msg: e instanceof Error ? e.message : String(e) });
|
||||
if (showToast) showToast(msg, 'error');
|
||||
else console.error(msg);
|
||||
} finally {
|
||||
@@ -158,16 +164,16 @@ function SpacePiecesPanel({ spaceId, showToast }: { spaceId: string; showToast?:
|
||||
<div className="flex h-full min-h-0">
|
||||
{/* 左: 一覧 */}
|
||||
<div className="w-48 shrink-0 overflow-y-auto border-r border-hairline p-2">
|
||||
<div className="mb-1 px-2 text-2xs font-semibold uppercase tracking-wide text-slate-500">Default</div>
|
||||
{defaults.length === 0 && <div className="px-2 text-xs text-slate-400">(なし)</div>}
|
||||
<div className="mb-1 px-2 text-2xs font-semibold uppercase tracking-wide text-slate-500">{t('settings.pieces.default')}</div>
|
||||
{defaults.length === 0 && <div className="px-2 text-xs text-slate-400">{t('settings.pieces.none')}</div>}
|
||||
{defaults.map(p => renderRow(p, true))}
|
||||
|
||||
<div className="mb-1 mt-3 flex items-center justify-between px-2">
|
||||
<span className="text-2xs font-semibold uppercase tracking-wide text-slate-500">Custom</span>
|
||||
<span className="text-2xs font-semibold uppercase tracking-wide text-slate-500">{t('settings.pieces.custom')}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCreating(true)}
|
||||
title="新しい Piece"
|
||||
title={t('settings.pieces.new')}
|
||||
className="flex h-5 w-5 items-center justify-center rounded text-slate-500 hover:bg-surface-2 hover:text-slate-900 text-sm leading-none transition-colors"
|
||||
>
|
||||
+
|
||||
@@ -189,7 +195,7 @@ function SpacePiecesPanel({ spaceId, showToast }: { spaceId: string; showToast?:
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{customs.length === 0 && !isCreating && <div className="px-2 text-xs text-slate-400">(なし)</div>}
|
||||
{customs.length === 0 && !isCreating && <div className="px-2 text-xs text-slate-400">{t('settings.pieces.none')}</div>}
|
||||
{customs.map(p => renderRow(p, false))}
|
||||
</div>
|
||||
|
||||
@@ -204,7 +210,7 @@ function SpacePiecesPanel({ spaceId, showToast }: { spaceId: string; showToast?:
|
||||
onDeleted={() => setSelected(null)}
|
||||
/>
|
||||
) : (
|
||||
<div className="text-sm text-slate-400">左から Piece を選んでください。</div>
|
||||
<div className="text-sm text-slate-400">{t('settings.pieces.selectHint')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user