sync: update from private repo (fd190dc)
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
This commit is contained in:
parent
afae52873b
commit
caa0d03900
@ -129,6 +129,36 @@ describe('piece-runner review feedback flow', () => {
|
||||
expect(instructions[4]).toContain('review 2: add conclusion');
|
||||
});
|
||||
|
||||
it('defaults ownerId and userId to "local" for owner-less (no-auth) jobs', async () => {
|
||||
workspacePath = makeWorkspace();
|
||||
let capturedCtx: { ownerId?: unknown; userId?: unknown } | undefined;
|
||||
executeMovementMock.mockImplementation(async (_movement, _instruction, _client, ctx) => {
|
||||
capturedCtx = ctx as typeof capturedCtx;
|
||||
return { next: 'COMPLETE', output: 'done', toolsUsed: [] };
|
||||
});
|
||||
|
||||
// options omit ownerId/userId entirely — a no-auth job has no owner.
|
||||
// (runPiece args: piece, instruction, client, workspace, callbacks, toolsConfig, options)
|
||||
await runPiece(makePiece(), 'TASK', {} as never, workspacePath, undefined, undefined, {});
|
||||
|
||||
expect(capturedCtx?.ownerId).toBe('local');
|
||||
expect(capturedCtx?.userId).toBe('local');
|
||||
});
|
||||
|
||||
it('preserves a real ownerId/userId when the job is owned (auth mode)', async () => {
|
||||
workspacePath = makeWorkspace();
|
||||
let capturedCtx: { ownerId?: unknown; userId?: unknown } | undefined;
|
||||
executeMovementMock.mockImplementation(async (_movement, _instruction, _client, ctx) => {
|
||||
capturedCtx = ctx as typeof capturedCtx;
|
||||
return { next: 'COMPLETE', output: 'done', toolsUsed: [] };
|
||||
});
|
||||
|
||||
await runPiece(makePiece(), 'TASK', {} as never, workspacePath, undefined, undefined, { ownerId: 'alice', userId: 'alice' });
|
||||
|
||||
expect(capturedCtx?.ownerId).toBe('alice');
|
||||
expect(capturedCtx?.userId).toBe('alice');
|
||||
});
|
||||
|
||||
it('appends safe git status and diff context after verify loops', async () => {
|
||||
workspacePath = makeGitWorkspace();
|
||||
const instructions: string[] = [];
|
||||
|
||||
@ -725,12 +725,16 @@ function prepareMovementContext(
|
||||
spawnSubTask: options?.spawnSubTask,
|
||||
missionBrief: options?.missionBrief,
|
||||
taskId: options?.taskId,
|
||||
userId: options?.userId,
|
||||
// No-auth jobs have no owner (ownerId/userId null). Resolve a single 'local'
|
||||
// identity — matching where pieces already resolve (worker.ts: ownerId ?? 'local')
|
||||
// — so MCP (global servers via ctx.ownerId) and the user folder / memory / notes
|
||||
// (ctx.userId) work in single-tenant deployments instead of being disabled.
|
||||
userId: options?.userId ?? 'local',
|
||||
browserSessionState: options?.browserSessionState,
|
||||
browserSessionProfileId: options?.browserSessionProfileId,
|
||||
browserSessionProfile: options?.browserSessionProfile,
|
||||
onAuthExpired: options?.onAuthExpired,
|
||||
ownerId: options?.ownerId,
|
||||
ownerId: options?.ownerId ?? 'local',
|
||||
jobId: options?.jobId,
|
||||
mcpConfig: options?.mcpConfig,
|
||||
mcpQuotaState: { files: 0, bytes: 0 },
|
||||
|
||||
@ -390,8 +390,8 @@ function AppInner({ isAdmin, authEnabled, user }: { isAdmin: boolean; authEnable
|
||||
{toast && (
|
||||
<div className={
|
||||
toast.variant === 'error'
|
||||
? 'mx-4 mt-2 px-4 py-2.5 bg-red-50 border border-red-200 rounded-xl text-[13px] text-red-800'
|
||||
: 'mx-4 mt-2 px-4 py-2.5 bg-green-50 border border-green-200 rounded-xl text-[13px] text-green-800'
|
||||
? 'mx-4 mt-2 px-4 py-2.5 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded-xl text-[13px] text-red-800 dark:text-red-300'
|
||||
: 'mx-4 mt-2 px-4 py-2.5 bg-green-50 dark:bg-green-500/15 border border-green-200 dark:border-green-500/30 rounded-xl text-[13px] text-green-800 dark:text-green-300'
|
||||
}>
|
||||
{toast.message}
|
||||
</div>
|
||||
|
||||
@ -10,13 +10,13 @@ import { MarkdownText } from '../../lib/markdown-text';
|
||||
const MD_KINDS = new Set<string>(['preview', 'final', 'ask', 'other']);
|
||||
|
||||
const KIND_COLORS: Record<string, { dot: string; badge: string; badgeText: string; border: string }> = {
|
||||
movement_start: { dot: 'bg-blue-600', badge: 'bg-blue-100', badgeText: 'text-blue-700', border: 'border-slate-200' },
|
||||
movement_complete:{ dot: 'bg-blue-600', badge: 'bg-blue-100', badgeText: 'text-blue-700', border: 'border-slate-200' },
|
||||
tool: { dot: 'bg-teal-600', badge: 'bg-teal-100', badgeText: 'text-teal-700', border: 'border-teal-200' },
|
||||
preview: { dot: 'bg-blue-600', badge: 'bg-blue-100', badgeText: 'text-blue-700', border: 'border-blue-200' },
|
||||
final: { dot: 'bg-green-600', badge: 'bg-green-100', badgeText: 'text-green-700', border: 'border-green-200' },
|
||||
ask: { dot: 'bg-amber-500', badge: 'bg-amber-100', badgeText: 'text-amber-700', border: 'border-amber-200' },
|
||||
preflight: { dot: 'bg-purple-400', badge: 'bg-purple-50', badgeText: 'text-purple-600', border: 'border-purple-100' },
|
||||
movement_start: { dot: 'bg-blue-600', badge: 'bg-blue-100 dark:bg-blue-500/15', badgeText: 'text-blue-700 dark:text-blue-300', border: 'border-slate-200' },
|
||||
movement_complete:{ dot: 'bg-blue-600', badge: 'bg-blue-100 dark:bg-blue-500/15', badgeText: 'text-blue-700 dark:text-blue-300', border: 'border-slate-200' },
|
||||
tool: { dot: 'bg-teal-600', badge: 'bg-teal-100 dark:bg-teal-500/15', badgeText: 'text-teal-700 dark:text-teal-300', border: 'border-teal-200 dark:border-teal-500/30' },
|
||||
preview: { dot: 'bg-blue-600', badge: 'bg-blue-100 dark:bg-blue-500/15', badgeText: 'text-blue-700 dark:text-blue-300', border: 'border-blue-200 dark:border-blue-500/30' },
|
||||
final: { dot: 'bg-green-600', badge: 'bg-green-100 dark:bg-green-500/15', badgeText: 'text-green-700 dark:text-green-300', border: 'border-green-200 dark:border-green-500/30' },
|
||||
ask: { dot: 'bg-amber-500', badge: 'bg-amber-100 dark:bg-amber-500/15', badgeText: 'text-amber-700 dark:text-amber-300', border: 'border-amber-200 dark:border-amber-500/30' },
|
||||
preflight: { dot: 'bg-purple-400', badge: 'bg-purple-50 dark:bg-purple-500/15', badgeText: 'text-purple-600 dark:text-purple-300', border: 'border-purple-100 dark:border-purple-500/30' },
|
||||
other: { dot: 'bg-slate-400', badge: 'bg-slate-100', badgeText: 'text-slate-600', border: 'border-slate-200' },
|
||||
};
|
||||
|
||||
|
||||
@ -96,8 +96,8 @@ export function BrowserSessionPanel() {
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="font-mono text-xs">{session.id.slice(0, 8)}...</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs ${
|
||||
session.state === 'user_interactive' ? 'bg-yellow-100 text-yellow-700' :
|
||||
session.state === 'agent_controlled' ? 'bg-blue-100 text-blue-700' :
|
||||
session.state === 'user_interactive' ? 'bg-yellow-100 dark:bg-yellow-500/15 text-yellow-700 dark:text-yellow-300' :
|
||||
session.state === 'agent_controlled' ? 'bg-blue-100 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
{session.state}
|
||||
@ -121,7 +121,7 @@ export function BrowserSessionPanel() {
|
||||
)}
|
||||
<button
|
||||
onClick={() => deleteMutation.mutate(session.id)}
|
||||
className="px-2 py-1 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200"
|
||||
className="px-2 py-1 text-xs bg-red-100 dark:bg-red-500/15 text-red-700 dark:text-red-300 rounded hover:bg-red-200 dark:hover:bg-red-500/25"
|
||||
>
|
||||
Destroy
|
||||
</button>
|
||||
|
||||
@ -179,12 +179,12 @@ function ChecklistCard({ comment }: { comment: LocalTaskComment }) {
|
||||
{/* Summary badges */}
|
||||
<div className="flex gap-1.5 mt-2 text-2xs">
|
||||
{summary.done > 0 && (
|
||||
<span className="inline-flex items-center gap-1 bg-emerald-50 text-emerald-700 border border-emerald-100 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<span className="inline-flex items-center gap-1 bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border border-emerald-100 dark:border-emerald-500/30 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<StatusIcon status="done" />{summary.done}
|
||||
</span>
|
||||
)}
|
||||
{summary.failed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 bg-red-50 text-red-700 border border-red-100 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<span className="inline-flex items-center gap-1 bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300 border border-red-100 dark:border-red-500/30 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<StatusIcon status="failed" />{summary.failed}
|
||||
</span>
|
||||
)}
|
||||
@ -343,7 +343,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
const isPending = !comment.injectedAt;
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<div className="max-w-[82%] bg-amber-50 border border-amber-200 text-slate-900 rounded-2xl rounded-br-md px-4 py-3">
|
||||
<div className="max-w-[82%] bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 text-slate-900 rounded-2xl rounded-br-md px-4 py-3">
|
||||
<div className="text-2xs text-amber-500 mb-1.5">
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
@ -374,7 +374,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
if (kind === 'ask') {
|
||||
return (
|
||||
<div className="flex justify-start">
|
||||
<div className="max-w-[82%] bg-amber-50 border border-amber-200 text-slate-900 rounded-2xl rounded-bl-md px-4 py-3 shadow-sm">
|
||||
<div className="max-w-[82%] bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 text-slate-900 rounded-2xl rounded-bl-md px-4 py-3 shadow-sm">
|
||||
<div className="text-2xs text-amber-500 mb-1.5">
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
@ -388,7 +388,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
if (kind === 'result') {
|
||||
return (
|
||||
<div className="flex justify-start">
|
||||
<div className="w-full bg-green-50 border border-green-200 text-slate-900 rounded-xl px-4 py-3 shadow-sm">
|
||||
<div className="w-full bg-green-50 dark:bg-green-500/15 border border-green-200 dark:border-green-500/30 text-slate-900 rounded-xl px-4 py-3 shadow-sm">
|
||||
<div className="text-2xs text-green-500 mb-1.5">
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
|
||||
@ -244,14 +244,14 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
|
||||
{isBusy && (
|
||||
<div className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 rounded border ${
|
||||
isWaitingSubtasks
|
||||
? 'border-indigo-100 bg-indigo-50'
|
||||
: 'border-emerald-100 bg-emerald-50'
|
||||
? 'border-indigo-100 dark:border-indigo-500/30 bg-indigo-50 dark:bg-indigo-500/15'
|
||||
: 'border-emerald-100 dark:border-emerald-500/30 bg-emerald-50 dark:bg-emerald-500/15'
|
||||
}`}>
|
||||
<span className={`w-1.5 h-1.5 rounded-full animate-pulse ${
|
||||
isWaitingSubtasks ? 'bg-indigo-500' : 'bg-emerald-500'
|
||||
}`} />
|
||||
<span className={`text-[10px] font-medium ${
|
||||
isWaitingSubtasks ? 'text-indigo-700' : 'text-emerald-700'
|
||||
isWaitingSubtasks ? 'text-indigo-700 dark:text-indigo-300' : 'text-emerald-700 dark:text-emerald-300'
|
||||
}`}>
|
||||
{isWaitingSubtasks ? 'subtasks' : 'running'}
|
||||
</span>
|
||||
@ -385,8 +385,8 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
|
||||
{isBusy && (
|
||||
<div className={`flex items-center gap-2 mb-2 px-2.5 py-1 rounded-md text-2xs ${
|
||||
canInterject
|
||||
? 'bg-amber-50 border border-amber-100 text-amber-700'
|
||||
: 'bg-blue-50 border border-blue-100 text-blue-700'
|
||||
? 'bg-amber-50 dark:bg-amber-500/15 border border-amber-100 dark:border-amber-500/30 text-amber-700 dark:text-amber-300'
|
||||
: 'bg-blue-50 dark:bg-blue-500/15 border border-blue-100 dark:border-blue-500/30 text-blue-700 dark:text-blue-300'
|
||||
}`}>
|
||||
<svg className="w-3 h-3 animate-spin flex-shrink-0" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
@ -396,13 +396,13 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
|
||||
</div>
|
||||
)}
|
||||
{sendError && !isBusy && (
|
||||
<div className="flex items-center justify-between gap-2 mb-2 px-2.5 py-1 bg-red-50 border border-red-100 rounded-md text-2xs text-red-700">
|
||||
<div className="flex items-center justify-between gap-2 mb-2 px-2.5 py-1 bg-red-50 dark:bg-red-500/15 border border-red-100 dark:border-red-500/30 rounded-md text-2xs text-red-700 dark:text-red-300">
|
||||
<span className="truncate">⚠ {sendError}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={submitting}
|
||||
className="flex-shrink-0 px-2 h-6 bg-canvas border border-red-200 rounded text-[10px] font-medium text-red-700 hover:bg-red-100 disabled:opacity-50"
|
||||
className="flex-shrink-0 px-2 h-6 bg-canvas border border-red-200 rounded text-[10px] font-medium text-red-700 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-500/15 disabled:opacity-50"
|
||||
>
|
||||
再送信
|
||||
</button>
|
||||
@ -461,7 +461,7 @@ export function ChatPane({ task, comments, onSubmit, onCancel, onOpenDetail }: C
|
||||
<button
|
||||
disabled={cancelling}
|
||||
onClick={() => void handleCancel()}
|
||||
className="px-3 h-9 bg-canvas border border-red-200 text-red-700 rounded-md text-xs font-semibold disabled:opacity-50 hover:bg-red-50 flex-shrink-0 transition-colors"
|
||||
className="px-3 h-9 bg-canvas border border-red-200 text-red-700 dark:text-red-300 rounded-md text-xs font-semibold disabled:opacity-50 hover:bg-red-50 dark:hover:bg-red-500/15 flex-shrink-0 transition-colors"
|
||||
title="エージェントの実行を停止"
|
||||
>
|
||||
{cancelling ? '停止中...' : '停止'}
|
||||
|
||||
@ -92,17 +92,17 @@ export function SubtaskInlineCard({ subtasks, subtaskCount, subtaskCompleted }:
|
||||
{/* Summary badges */}
|
||||
<div className="flex gap-1.5 mt-2 text-2xs">
|
||||
{subtaskCompleted > 0 && (
|
||||
<span className="inline-flex items-center gap-1 bg-emerald-50 text-emerald-700 border border-emerald-100 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<span className="inline-flex items-center gap-1 bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border border-emerald-100 dark:border-emerald-500/30 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<SubtaskStatusIcon status="succeeded" />{subtaskCompleted}
|
||||
</span>
|
||||
)}
|
||||
{running > 0 && (
|
||||
<span className="inline-flex items-center gap-1 bg-blue-50 text-blue-700 border border-blue-100 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<span className="inline-flex items-center gap-1 bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 border border-blue-100 dark:border-blue-500/30 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<SubtaskStatusIcon status="running" />{running}
|
||||
</span>
|
||||
)}
|
||||
{failed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 bg-red-50 text-red-700 border border-red-100 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<span className="inline-flex items-center gap-1 bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300 border border-red-100 dark:border-red-500/30 px-1.5 py-0.5 rounded font-mono tabular-nums">
|
||||
<SubtaskStatusIcon status="failed" />{failed}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -110,7 +110,7 @@ function ToolCallRow({ tc }: { tc: ToolCallData }) {
|
||||
{tc.isError ? '✕' : '✓'}
|
||||
</span>
|
||||
{display.server && (
|
||||
<span className="font-mono text-[10px] text-purple-500 bg-purple-50 px-1 py-px rounded flex-shrink-0">
|
||||
<span className="font-mono text-[10px] text-purple-500 dark:text-purple-300 bg-purple-50 dark:bg-purple-500/15 px-1 py-px rounded flex-shrink-0">
|
||||
{display.server}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -200,7 +200,7 @@ export function CreateTaskDialog({ onClose, onSubmit, initialPiece, initialBody,
|
||||
|
||||
{/* MCP warnings (always visible when applicable) */}
|
||||
{missingMcp.length > 0 && (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-300 rounded text-xs text-yellow-900 space-y-2">
|
||||
<div className="p-3 bg-yellow-50 dark:bg-yellow-500/15 border border-yellow-300 dark:border-yellow-500/30 rounded text-xs text-yellow-900 dark:text-yellow-300 space-y-2">
|
||||
<div>
|
||||
<strong>このタスクには MCP 連携が必要です:</strong> {missingMcp.join(', ')}
|
||||
</div>
|
||||
@ -217,7 +217,7 @@ export function CreateTaskDialog({ onClose, onSubmit, initialPiece, initialBody,
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-2xs text-yellow-700">
|
||||
<div className="text-2xs text-yellow-700 dark:text-yellow-300">
|
||||
未連携のままタスクを作成すると、waiting_human 状態で停止し、連携後に自動で再開します。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -69,7 +69,7 @@ export function MarkdownWidget({ widget, onSave, onDelete }: Props) {
|
||||
if (!window.confirm(`"${widget.title}" を削除しますか?`)) return;
|
||||
await onDelete();
|
||||
}}
|
||||
className="px-3 py-1 text-xs text-red-600 hover:bg-red-50 rounded"
|
||||
className="px-3 py-1 text-xs text-red-600 hover:bg-red-50 dark:hover:bg-red-500/15 rounded"
|
||||
aria-label="削除"
|
||||
>
|
||||
🗑 削除
|
||||
|
||||
@ -118,7 +118,7 @@ function WorkerRow({
|
||||
)}
|
||||
<span className="truncate">{name}</span>
|
||||
{proxy && (
|
||||
<span className="px-1 py-0.5 rounded text-[9px] font-medium bg-violet-50 text-violet-700 leading-none">
|
||||
<span className="px-1 py-0.5 rounded text-[9px] font-medium bg-violet-50 dark:bg-violet-500/15 text-violet-700 dark:text-violet-300 leading-none">
|
||||
proxy
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -150,7 +150,7 @@ export function ContinueWithPieceDialog({
|
||||
</div>
|
||||
|
||||
{submitError && (
|
||||
<div className="text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1.5">
|
||||
<div className="text-xs text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded px-2 py-1.5">
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -102,7 +102,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
onClick={handleCopy}
|
||||
title={copied ? 'コピーしました' : '共有リンクをコピー'}
|
||||
aria-label="共有リンクをコピー"
|
||||
className={`${iconBtnBase} ${copied ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface'}`}
|
||||
className={`${iconBtnBase} ${copied ? 'border-emerald-200 dark:border-emerald-500/30 bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300' : 'border-hairline bg-canvas text-slate-600 hover:text-slate-900 hover:bg-surface'}`}
|
||||
>
|
||||
{copied ? (
|
||||
<svg className="w-3.5 h-3.5" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
@ -120,7 +120,7 @@ function ShareButton({ taskId, shareToken, onShareChange }: { taskId: number; sh
|
||||
disabled={unshareMutation.isPending}
|
||||
title="共有を停止"
|
||||
aria-label="共有を停止"
|
||||
className={`${iconBtnBase} border-hairline bg-canvas text-slate-500 hover:text-red-700 hover:border-red-200 hover:bg-red-50`}
|
||||
className={`${iconBtnBase} border-hairline bg-canvas text-slate-500 hover:text-red-700 dark:hover:text-red-300 hover:border-red-200 hover:bg-red-50 dark:hover:bg-red-500/15`}
|
||||
>
|
||||
{unshareMutation.isPending ? (
|
||||
<svg className="w-3.5 h-3.5 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
|
||||
@ -279,7 +279,7 @@ export function LocalDetailPanel({
|
||||
<button
|
||||
disabled={deleting}
|
||||
onClick={handleDelete}
|
||||
className="px-3 h-7 bg-canvas border border-red-200 text-red-700 rounded-md text-xs font-medium disabled:opacity-50 hover:bg-red-50 transition-colors"
|
||||
className="px-3 h-7 bg-canvas border border-red-200 text-red-700 dark:text-red-300 rounded-md text-xs font-medium disabled:opacity-50 hover:bg-red-50 dark:hover:bg-red-500/15 transition-colors"
|
||||
>
|
||||
{deleting ? '削除中...' : '削除'}
|
||||
</button>
|
||||
|
||||
@ -41,7 +41,7 @@ export function ReflectionBadge({ taskId }: ReflectionBadgeProps) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-amber-50 px-2 py-0.5 text-xs text-amber-700 hover:bg-amber-100"
|
||||
className="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-500/15 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-500/15"
|
||||
>
|
||||
🧠 {label}
|
||||
</a>
|
||||
|
||||
@ -67,7 +67,7 @@ export function BrowserTab({ taskId }: { taskId: number }) {
|
||||
if (isError) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
return (
|
||||
<div className="p-4 text-sm text-red-700">
|
||||
<div className="p-4 text-sm text-red-700 dark:text-red-300">
|
||||
ブラウザセッション情報の取得に失敗しました: {msg}
|
||||
</div>
|
||||
);
|
||||
@ -77,7 +77,7 @@ export function BrowserTab({ taskId }: { taskId: number }) {
|
||||
if (data?.reason === 'novnc_not_installed') {
|
||||
return (
|
||||
<div className="bg-canvas border border-amber-300 rounded-md p-6 text-sm text-slate-700">
|
||||
<p className="font-medium text-amber-800 mb-2">noVNC の Web 配布物 (vnc.html) が配置されていません</p>
|
||||
<p className="font-medium text-amber-800 dark:text-amber-300 mb-2">noVNC の Web 配布物 (vnc.html) が配置されていません</p>
|
||||
<p className="text-xs leading-relaxed mb-2">
|
||||
このタスクのブラウザセッションは存在しますが、noVNC の HTML/JS 一式が
|
||||
<code className="mx-1 px-1 rounded bg-slate-100 font-mono text-2xs">vendor/noVNC/</code>
|
||||
|
||||
@ -83,7 +83,7 @@ function FeedbackPanel({ task }: { task: LocalTask }) {
|
||||
<button
|
||||
onClick={() => handleRatingClick('good')}
|
||||
className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${
|
||||
rating === 'good' ? 'bg-green-50 border-green-300 text-green-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'
|
||||
rating === 'good' ? 'bg-green-50 dark:bg-green-500/15 border-green-300 dark:border-green-500/30 text-green-700 dark:text-green-300' : 'border-slate-200 text-slate-500 hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
👍 良かった
|
||||
@ -91,7 +91,7 @@ function FeedbackPanel({ task }: { task: LocalTask }) {
|
||||
<button
|
||||
onClick={() => handleRatingClick('bad')}
|
||||
className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${
|
||||
rating === 'bad' ? 'bg-red-50 border-red-300 text-red-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'
|
||||
rating === 'bad' ? 'bg-red-50 dark:bg-red-500/15 border-red-300 dark:border-red-500/30 text-red-700 dark:text-red-300' : 'border-slate-200 text-slate-500 hover:border-slate-300'
|
||||
}`}
|
||||
>
|
||||
👎 改善が必要
|
||||
|
||||
@ -59,13 +59,13 @@ function parseEventsJsonl(raw: string): ParseSummary {
|
||||
// Categorize for filter chips and color coding.
|
||||
const CATEGORIES: Array<{ id: string; label: string; kinds: string[]; tone: string }> = [
|
||||
{ id: 'run', label: 'Run', kinds: ['run_start', 'run_complete'], tone: 'bg-surface-2 text-slate-700 border-hairline' },
|
||||
{ id: 'movement', label: 'Movement', kinds: ['movement_start', 'movement_complete', 'transition', 'complete'], tone: 'bg-blue-50 text-blue-800 border-blue-100' },
|
||||
{ id: 'movement', label: 'Movement', kinds: ['movement_start', 'movement_complete', 'transition', 'complete'], tone: 'bg-blue-50 text-blue-800 border-blue-100 dark:bg-blue-500/15 dark:text-blue-300 dark:border-blue-500/30' },
|
||||
{ id: 'tool', label: 'Tool', kinds: ['tool_call', 'tool_result'], tone: 'bg-canvas text-slate-700 border-hairline' },
|
||||
{ id: 'llm', label: 'LLM', kinds: ['llm_call_start', 'llm_call_end'], tone: 'bg-indigo-50 text-indigo-800 border-indigo-100' },
|
||||
{ id: 'cache', label: 'Cache', kinds: ['cache_set', 'cache_hit', 'cache_invalidate'], tone: 'bg-amber-50 text-amber-800 border-amber-100' },
|
||||
{ id: 'memory', label: 'Memory', kinds: ['memory_invalidate', 'memory_update_call', 'memory_handoff_write', 'memory_handoff_read', 'memory_delta_write', 'memory_delta_absorb', 'memory_snapshot_written', 'memory_snapshot_failed'], tone: 'bg-emerald-50 text-emerald-800 border-emerald-100' },
|
||||
{ id: 'watchdog', label: 'Watchdog', kinds: ['watchdog_fire', 'followup_detected'], tone: 'bg-red-50 text-red-800 border-red-100' },
|
||||
{ id: 'context', label: 'Context', kinds: ['context_action'], tone: 'bg-violet-50 text-violet-800 border-violet-100' },
|
||||
{ id: 'llm', label: 'LLM', kinds: ['llm_call_start', 'llm_call_end'], tone: 'bg-indigo-50 text-indigo-800 border-indigo-100 dark:bg-indigo-500/15 dark:text-indigo-300 dark:border-indigo-500/30' },
|
||||
{ id: 'cache', label: 'Cache', kinds: ['cache_set', 'cache_hit', 'cache_invalidate'], tone: 'bg-amber-50 text-amber-800 border-amber-100 dark:bg-amber-500/15 dark:text-amber-300 dark:border-amber-500/30' },
|
||||
{ id: 'memory', label: 'Memory', kinds: ['memory_invalidate', 'memory_update_call', 'memory_handoff_write', 'memory_handoff_read', 'memory_delta_write', 'memory_delta_absorb', 'memory_snapshot_written', 'memory_snapshot_failed'], tone: 'bg-emerald-50 text-emerald-800 border-emerald-100 dark:bg-emerald-500/15 dark:text-emerald-300 dark:border-emerald-500/30' },
|
||||
{ id: 'watchdog', label: 'Watchdog', kinds: ['watchdog_fire', 'followup_detected'], tone: 'bg-red-50 text-red-800 border-red-100 dark:bg-red-500/15 dark:text-red-300 dark:border-red-500/30' },
|
||||
{ id: 'context', label: 'Context', kinds: ['context_action'], tone: 'bg-violet-50 text-violet-800 border-violet-100 dark:bg-violet-500/15 dark:text-violet-300 dark:border-violet-500/30' },
|
||||
];
|
||||
|
||||
/**
|
||||
@ -415,7 +415,7 @@ export function TraceTab({ taskId }: TraceTabProps) {
|
||||
const bar = durationBarStyle(toolTimings.llm.totalMs);
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-2xs font-mono">
|
||||
<span className="min-w-[14ch] text-indigo-700 shrink-0">llm × {toolTimings.llm.count}</span>
|
||||
<span className="min-w-[14ch] text-indigo-700 dark:text-indigo-300 shrink-0">llm × {toolTimings.llm.count}</span>
|
||||
<div className="flex-1 min-w-0 h-2 bg-slate-100 rounded relative overflow-hidden">
|
||||
<div className={`${bar.tone} h-full`} style={{ width: `${bar.widthPct}%` }} />
|
||||
</div>
|
||||
@ -429,7 +429,7 @@ export function TraceTab({ taskId }: TraceTabProps) {
|
||||
const bar = durationBarStyle(totalMs);
|
||||
return (
|
||||
<div key={tool} className="flex items-center gap-2 text-2xs font-mono">
|
||||
<span className={`min-w-[14ch] shrink-0 truncate ${errors > 0 ? 'text-red-700' : 'text-slate-700'}`} title={tool}>
|
||||
<span className={`min-w-[14ch] shrink-0 truncate ${errors > 0 ? 'text-red-700 dark:text-red-300' : 'text-slate-700'}`} title={tool}>
|
||||
{tool} × {count}
|
||||
{errors > 0 ? <span className="text-red-600"> ⚠{errors}</span> : null}
|
||||
</span>
|
||||
|
||||
@ -24,7 +24,7 @@ export function ConsoleHeader({ state, status }: { state: ConnState; status: Con
|
||||
return <div className="px-3 py-2 text-sm text-amber-600">{state.kind === 'connecting' ? 'Connecting…' : 'Restoring scrollback…'}</div>;
|
||||
}
|
||||
if (state.kind === 'disconnected') {
|
||||
return <div className="px-3 py-2 text-sm text-red-700">Disconnected ({state.reason ?? 'unknown'}).</div>;
|
||||
return <div className="px-3 py-2 text-sm text-red-700 dark:text-red-300">Disconnected ({state.reason ?? 'unknown'}).</div>;
|
||||
}
|
||||
const startedAt = status?.started_at ? new Date(status.started_at).getTime() : now;
|
||||
const lastAt = status?.last_activity_at ? new Date(status.last_activity_at).getTime() : now;
|
||||
|
||||
@ -56,7 +56,7 @@ export function AmazonProductsCard({ data, onExpand }: { data: AmazonData; onExp
|
||||
<div className="text-center mt-2">
|
||||
<button
|
||||
onClick={onExpand}
|
||||
className="text-blue-500 hover:text-blue-700 cursor-pointer bg-transparent border-none"
|
||||
className="text-blue-500 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer bg-transparent border-none"
|
||||
style={{ fontSize: 11 }}
|
||||
>
|
||||
▼ 詳細を表示
|
||||
|
||||
@ -32,7 +32,7 @@ export function MapPlacesCard({ data, onExpand }: { data: MapData; onExpand: ()
|
||||
<div className="text-center mt-2">
|
||||
<button
|
||||
onClick={onExpand}
|
||||
className="text-blue-500 hover:text-blue-700 cursor-pointer bg-transparent border-none"
|
||||
className="text-blue-500 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer bg-transparent border-none"
|
||||
style={{ fontSize: 11 }}
|
||||
>
|
||||
▼ 地図で表示
|
||||
|
||||
@ -55,7 +55,7 @@ export function XPostsCard({ data, onExpand }: { data: XPostData; onExpand: () =
|
||||
<div className="text-center mt-2">
|
||||
<button
|
||||
onClick={onExpand}
|
||||
className="text-blue-500 hover:text-blue-700 cursor-pointer bg-transparent border-none"
|
||||
className="text-blue-500 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer bg-transparent border-none"
|
||||
style={{ fontSize: 11 }}
|
||||
>
|
||||
▼ 詳細を表示
|
||||
|
||||
@ -59,7 +59,7 @@ export function YouTubeVideosCard({ data, onExpand }: { data: YouTubeData; onExp
|
||||
<div className="text-center mt-2">
|
||||
<button
|
||||
onClick={onExpand}
|
||||
className="text-blue-500 hover:text-blue-700 cursor-pointer bg-transparent border-none"
|
||||
className="text-blue-500 hover:text-blue-700 dark:hover:text-blue-300 cursor-pointer bg-transparent border-none"
|
||||
style={{ fontSize: 11 }}
|
||||
>
|
||||
▼ 詳細を表示
|
||||
|
||||
@ -517,9 +517,9 @@ ${bodyHtml}
|
||||
|
||||
// --- JSONL ---
|
||||
function badgeClass(color: 'green' | 'orange' | 'red' | 'gray'): string {
|
||||
if (color === 'green') return 'bg-green-100 text-green-800';
|
||||
if (color === 'orange') return 'bg-amber-100 text-amber-800';
|
||||
if (color === 'red') return 'bg-red-100 text-red-800';
|
||||
if (color === 'green') return 'bg-green-100 text-green-800 dark:bg-green-500/15 dark:text-green-300';
|
||||
if (color === 'orange') return 'bg-amber-100 text-amber-800 dark:bg-amber-500/15 dark:text-amber-300';
|
||||
if (color === 'red') return 'bg-red-100 text-red-800 dark:bg-red-500/15 dark:text-red-300';
|
||||
return 'bg-slate-100 text-slate-600';
|
||||
}
|
||||
|
||||
@ -782,7 +782,7 @@ export function FilePreview({ name, content, imageSrc, markdownImageBaseUrl, onC
|
||||
</div>
|
||||
<div className="p-4 overflow-auto flex-1">
|
||||
{mode === 'view' && error && (
|
||||
<div className="mb-2 px-3 py-2 bg-red-50 border border-red-200 text-red-700 text-xs rounded">{error}</div>
|
||||
<div className="mb-2 px-3 py-2 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 text-red-700 dark:text-red-300 text-xs rounded">{error}</div>
|
||||
)}
|
||||
{body}
|
||||
</div>
|
||||
|
||||
@ -52,15 +52,15 @@ export const LocalTaskListItem = memo(function LocalTaskListItem({ task, active,
|
||||
<span className="text-slate-400">system</span>
|
||||
)}
|
||||
{task.visibility === 'private' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-amber-50 text-amber-700 border border-amber-100" title="Private">private</span>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300 border border-amber-100 dark:border-amber-500/30" title="Private">private</span>
|
||||
)}
|
||||
{task.visibility === 'org' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-blue-50 text-blue-700 border border-blue-100" title={`Shared with ${task.visibilityScopeOrgName ?? 'org'}`}>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 border border-blue-100 dark:border-blue-500/30" title={`Shared with ${task.visibilityScopeOrgName ?? 'org'}`}>
|
||||
{task.visibilityScopeOrgName ?? 'org'}
|
||||
</span>
|
||||
)}
|
||||
{task.visibility === 'public' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-emerald-50 text-emerald-700 border border-emerald-100" title="Public">public</span>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border border-emerald-100 dark:border-emerald-500/30" title="Public">public</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@ -124,7 +124,7 @@ function AssetUploader({
|
||||
type="button"
|
||||
onClick={() => void handleClear()}
|
||||
disabled={busy}
|
||||
className="px-2.5 h-7 text-2xs font-medium text-red-700 border border-red-200 bg-canvas hover:bg-red-50 rounded-md disabled:opacity-50 transition-colors"
|
||||
className="px-2.5 h-7 text-2xs font-medium text-red-700 dark:text-red-300 border border-red-200 bg-canvas hover:bg-red-50 dark:hover:bg-red-500/15 rounded-md disabled:opacity-50 transition-colors"
|
||||
>
|
||||
削除
|
||||
</button>
|
||||
|
||||
@ -242,16 +242,16 @@ function ConfigFormInner({ section }: ConfigFormProps) {
|
||||
<div
|
||||
className={`sticky bottom-0 px-3 py-2.5 mt-6 border rounded-md flex items-center justify-end gap-2 transition-colors ${
|
||||
dirty
|
||||
? 'bg-amber-50 border-amber-300 shadow-[0_2px_8px_rgba(180,83,9,0.08)]'
|
||||
? 'bg-amber-50 dark:bg-amber-500/15 border-amber-300 dark:border-amber-500/30 shadow-[0_2px_8px_rgba(180,83,9,0.08)]'
|
||||
: 'bg-canvas border-hairline'
|
||||
}`}
|
||||
>
|
||||
{toast ? (
|
||||
<span className={`text-2xs mr-auto ${toast.startsWith('エラー') ? 'text-red-600' : 'text-emerald-700'}`}>
|
||||
<span className={`text-2xs mr-auto ${toast.startsWith('エラー') ? 'text-red-600' : 'text-emerald-700 dark:text-emerald-300'}`}>
|
||||
{toast}
|
||||
</span>
|
||||
) : dirty ? (
|
||||
<span className="text-xs mr-auto text-amber-800 flex items-center gap-1.5 font-medium min-w-0">
|
||||
<span className="text-xs mr-auto text-amber-800 dark:text-amber-300 flex items-center gap-1.5 font-medium min-w-0">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-amber-500 animate-pulse flex-shrink-0" aria-hidden />
|
||||
<span className="truncate">
|
||||
<span className="hidden sm:inline">未保存: {dirtyCount} 項目 — 「Save & Apply」を押すまで反映されません</span>
|
||||
|
||||
@ -106,9 +106,9 @@ export function GatewayKeyRawKeyDialog({ rawKey, team, reason, onClose }: Props)
|
||||
</h3>
|
||||
<p className="text-xs text-slate-500 mb-4">team: {team}</p>
|
||||
|
||||
<div className="rounded border border-red-300 bg-red-50 p-3 mb-3">
|
||||
<p className="text-sm text-red-800 font-medium">⚠️ このキーは今後二度と表示されません</p>
|
||||
<p className="text-xs text-red-700 mt-1">
|
||||
<div className="rounded border border-red-300 dark:border-red-500/30 bg-red-50 dark:bg-red-500/15 p-3 mb-3">
|
||||
<p className="text-sm text-red-800 dark:text-red-300 font-medium">⚠️ このキーは今後二度と表示されません</p>
|
||||
<p className="text-xs text-red-700 dark:text-red-300 mt-1">
|
||||
必ずパスワードマネージャや LLM クライアントの設定にコピー・保存してから閉じてください。
|
||||
紛失した場合は Rotate で再発行する必要があります。
|
||||
</p>
|
||||
|
||||
@ -282,9 +282,9 @@ export function GatewayKeysSection({ showToast }: Props) {
|
||||
</td>
|
||||
<td className="p-2 text-xs">
|
||||
{isRevoked ? (
|
||||
<span className="px-1.5 py-0.5 bg-red-50 text-red-700 rounded">revoked</span>
|
||||
<span className="px-1.5 py-0.5 bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300 rounded">revoked</span>
|
||||
) : (
|
||||
<span className="px-1.5 py-0.5 bg-green-50 text-green-700 rounded">active</span>
|
||||
<span className="px-1.5 py-0.5 bg-green-50 dark:bg-green-500/15 text-green-700 dark:text-green-300 rounded">active</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="p-2 text-right">
|
||||
@ -308,7 +308,7 @@ export function GatewayKeysSection({ showToast }: Props) {
|
||||
type="button"
|
||||
disabled={isRevoked}
|
||||
onClick={() => handleRevoke(row)}
|
||||
className="px-2 py-0.5 text-xs rounded border border-red-300 text-red-700 hover:bg-red-50 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
className="px-2 py-0.5 text-xs rounded border border-red-300 text-red-700 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-500/15 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
|
||||
@ -87,21 +87,21 @@ function StatusBadge({ status }: { status: GatewayServerStatus | undefined }) {
|
||||
}
|
||||
if (status.state === 'running') {
|
||||
return (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-emerald-50 text-emerald-700 border border-emerald-200">
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border border-emerald-200 dark:border-emerald-500/30">
|
||||
running (mounted at /v1, port {status.sharedPort})
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status.state === 'misconfigured') {
|
||||
return (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-red-50 text-red-700 border border-red-200">
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-500/30">
|
||||
misconfigured ({status.errors.length} error{status.errors.length === 1 ? '' : 's'})
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status.state === 'starting' || status.state === 'stopping') {
|
||||
return (
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-amber-50 text-amber-700 border border-amber-200">
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-500/30">
|
||||
{status.state}…
|
||||
</span>
|
||||
);
|
||||
@ -206,7 +206,7 @@ export function GatewayServerForm({ config, onChange }: SectionFormProps) {
|
||||
<StatusBadge status={statusQuery.data} />
|
||||
</div>
|
||||
{statusQuery.data?.errors && statusQuery.data.errors.length > 0 && (
|
||||
<ul className="mt-2 text-xs text-red-700 bg-red-50 border border-red-200 rounded p-2 space-y-0.5">
|
||||
<ul className="mt-2 text-xs text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded p-2 space-y-0.5">
|
||||
{statusQuery.data.errors.map((e, i) => (
|
||||
<li key={i}>• {e}</li>
|
||||
))}
|
||||
@ -260,7 +260,7 @@ export function GatewayServerForm({ config, onChange }: SectionFormProps) {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`border rounded-md p-3 space-y-2 relative ${errs.length > 0 ? 'border-red-200 bg-red-50/30' : 'border-slate-200'}`}
|
||||
className={`border rounded-md p-3 space-y-2 relative ${errs.length > 0 ? 'border-red-200 dark:border-red-500/30 bg-red-50/30 dark:bg-red-500/10' : 'border-slate-200'}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => removeBackend(i)}
|
||||
@ -306,7 +306,7 @@ export function GatewayServerForm({ config, onChange }: SectionFormProps) {
|
||||
reference into a literal "${VAR}" string and
|
||||
the env var indirection is lost. */}
|
||||
{typeof b.apiKey === 'string' && b.apiKey.trimStart().startsWith('${') && (
|
||||
<p className="text-2xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 mt-1">
|
||||
<p className="text-2xs text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 rounded px-2 py-1 mt-1">
|
||||
env var reference detected: 保存すると <code>{b.apiKey}</code> がそのまま config.yaml に書き込まれ、起動時の env 置換は効かなくなります。env 経由で渡すなら config.yaml を直接編集してください。
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -24,7 +24,7 @@ export function KnowledgeNamespacesForm({ config, onChange }: SectionFormProps)
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-base font-semibold text-slate-800">Knowledge (DKS)</h2>
|
||||
<span
|
||||
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wide bg-amber-100 text-amber-800 border border-amber-300"
|
||||
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wide bg-amber-100 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-500/30"
|
||||
title="この機能は legacy です。新規の知識検索統合は MCP server 経由を推奨"
|
||||
>
|
||||
LEGACY
|
||||
@ -33,14 +33,14 @@ export function KnowledgeNamespacesForm({ config, onChange }: SectionFormProps)
|
||||
|
||||
<div
|
||||
role="note"
|
||||
className="rounded border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900"
|
||||
className="rounded border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 px-3 py-2 text-xs text-amber-900 dark:text-amber-300"
|
||||
>
|
||||
DKS 機能は <strong>legacy</strong> 化されており、新規の知識検索統合は{' '}
|
||||
<strong>MCP server 経由</strong> を推奨します。既存の namespace 設定は引き続き動作しますが、
|
||||
新規 namespace の追加はできません。{' '}
|
||||
<a
|
||||
href="/help"
|
||||
className="underline text-amber-900 hover:text-amber-700"
|
||||
className="underline text-amber-900 dark:text-amber-300 hover:text-amber-700 dark:hover:text-amber-300"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@ -237,7 +237,7 @@ export function LlmWorkersForm({ config, onChange, overriddenByEnv }: SectionFor
|
||||
/>
|
||||
{endpointOverridden && <EnvOverrideWarning />}
|
||||
{showSelfLoop && (
|
||||
<p className="text-2xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 mt-1">
|
||||
<p className="text-2xs text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 rounded px-2 py-1 mt-1">
|
||||
endpoint は自インスタンスを指しているように見えます (self-loop)。
|
||||
リバースプロキシ越しの場合はこの警告は無視できます。
|
||||
</p>
|
||||
|
||||
@ -144,11 +144,11 @@ async function fetchMetrics(days: number = 30): Promise<ReflectionMetrics> {
|
||||
// ── Shared UI primitives ──────────────────────────────────────────────────────
|
||||
|
||||
const OUTCOME_LABELS: Record<string, { label: string; cls: string }> = {
|
||||
applied: { label: '適用済み', cls: 'bg-emerald-100 text-emerald-800' },
|
||||
partial: { label: '一部適用', cls: 'bg-yellow-100 text-yellow-800' },
|
||||
applied: { label: '適用済み', cls: 'bg-emerald-100 dark:bg-emerald-500/15 text-emerald-800 dark:text-emerald-300' },
|
||||
partial: { label: '一部適用', cls: 'bg-yellow-100 dark:bg-yellow-500/15 text-yellow-800 dark:text-yellow-300' },
|
||||
abstained: { label: '学習なし', cls: 'bg-slate-100 text-slate-600' },
|
||||
rejected: { label: '却下', cls: 'bg-red-100 text-red-700' },
|
||||
failed: { label: '失敗', cls: 'bg-red-200 text-red-900' },
|
||||
rejected: { label: '却下', cls: 'bg-red-100 dark:bg-red-500/15 text-red-700 dark:text-red-300' },
|
||||
failed: { label: '失敗', cls: 'bg-red-200 dark:bg-red-500/20 text-red-900 dark:text-red-300' },
|
||||
};
|
||||
|
||||
function OutcomeBadge({ outcome }: { outcome: string }) {
|
||||
@ -307,7 +307,7 @@ function MemoryEntryModal({
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-xs text-red-600 bg-red-50 border border-red-200 px-3 py-2 rounded-md">
|
||||
<div className="text-xs text-red-600 dark:text-red-300 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 px-3 py-2 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@ -411,7 +411,7 @@ function MemoryEntriesPanel() {
|
||||
)}
|
||||
|
||||
{deleteError && (
|
||||
<div className="px-4 py-2 text-xs text-red-600 bg-red-50">
|
||||
<div className="px-4 py-2 text-xs text-red-600 dark:text-red-300 bg-red-50 dark:bg-red-500/15">
|
||||
{deleteError}
|
||||
</div>
|
||||
)}
|
||||
@ -456,7 +456,7 @@ function MemoryEntriesPanel() {
|
||||
<button
|
||||
onClick={() => void handleDelete(entry.name)}
|
||||
disabled={deleting === entry.name}
|
||||
className="px-2 h-6 text-2xs text-red-700 border border-red-200 bg-canvas hover:bg-red-50 rounded transition-colors disabled:opacity-50"
|
||||
className="px-2 h-6 text-2xs text-red-700 dark:text-red-300 border border-red-200 bg-canvas hover:bg-red-50 dark:hover:bg-red-500/15 rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{deleting === entry.name ? '…' : '削除'}
|
||||
</button>
|
||||
@ -517,12 +517,12 @@ function SnapshotCard({ item, onReverted }: { item: SnapshotIndexEntry; onRevert
|
||||
</span>
|
||||
<span className="flex-shrink-0 flex items-center gap-1.5">
|
||||
{item.memoryChanges > 0 && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 bg-blue-50 text-blue-700 rounded">
|
||||
<span className="text-[10px] px-1.5 py-0.5 bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 rounded">
|
||||
{item.memoryChanges} mem
|
||||
</span>
|
||||
)}
|
||||
{item.pieceEdited && (
|
||||
<span className="text-[10px] px-1.5 py-0.5 bg-purple-50 text-purple-700 rounded">
|
||||
<span className="text-[10px] px-1.5 py-0.5 bg-purple-50 dark:bg-purple-500/15 text-purple-700 dark:text-purple-300 rounded">
|
||||
piece
|
||||
</span>
|
||||
)}
|
||||
@ -579,7 +579,7 @@ function SnapshotCard({ item, onReverted }: { item: SnapshotIndexEntry; onRevert
|
||||
</div>
|
||||
<ul className="space-y-0.5">
|
||||
{d.rejections.map((r, i) => (
|
||||
<li key={i} className="text-2xs text-red-700">
|
||||
<li key={i} className="text-2xs text-red-700 dark:text-red-300">
|
||||
<span className="font-mono">{r.code}</span>
|
||||
{r.name && <span className="text-slate-500 ml-1">({r.name})</span>}
|
||||
</li>
|
||||
@ -631,7 +631,7 @@ function SnapshotCard({ item, onReverted }: { item: SnapshotIndexEntry; onRevert
|
||||
{!item.reverted && (
|
||||
<div className="pt-1">
|
||||
{revertDone === true && (
|
||||
<span className="text-xs text-emerald-700">正常に revert しました。</span>
|
||||
<span className="text-xs text-emerald-700 dark:text-emerald-300">正常に revert しました。</span>
|
||||
)}
|
||||
{revertDone === false && (
|
||||
<span className="text-xs text-slate-500">すでに revert 済みです。</span>
|
||||
@ -640,14 +640,14 @@ function SnapshotCard({ item, onReverted }: { item: SnapshotIndexEntry; onRevert
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmRevert(true)}
|
||||
className="px-2.5 h-7 text-2xs text-amber-800 border border-amber-300 bg-amber-50 hover:bg-amber-100 rounded transition-colors"
|
||||
className="px-2.5 h-7 text-2xs text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 hover:bg-amber-100 dark:hover:bg-amber-500/15 rounded transition-colors"
|
||||
>
|
||||
このスナップショットを revert…
|
||||
</button>
|
||||
)}
|
||||
{revertDone === null && confirmRevert && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-amber-800">
|
||||
<span className="text-xs text-amber-800 dark:text-amber-300">
|
||||
このスナップショットの変更前の状態に戻しますか?
|
||||
</span>
|
||||
<button
|
||||
@ -729,12 +729,12 @@ function BeforeAfterDiff({
|
||||
</div>
|
||||
)}
|
||||
{isAdded && (
|
||||
<div className="text-2xs text-emerald-700 bg-emerald-50 border border-emerald-200 rounded px-2 py-1 mb-1">
|
||||
<div className="text-2xs text-emerald-700 dark:text-emerald-300 bg-emerald-50 dark:bg-emerald-500/15 border border-emerald-200 dark:border-emerald-500/30 rounded px-2 py-1 mb-1">
|
||||
追加
|
||||
</div>
|
||||
)}
|
||||
{isRemoved && (
|
||||
<div className="text-2xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 mb-1">
|
||||
<div className="text-2xs text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded px-2 py-1 mb-1">
|
||||
削除
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -146,7 +146,7 @@ export function ModelSelect({ value, onChange, endpoint, apiKeyRaw }: ModelSelec
|
||||
className="w-full h-8 px-2.5 text-[13px] border border-hairline rounded-md focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none bg-canvas"
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-2xs text-amber-700 bg-amber-50 border border-amber-100 px-2 py-1 rounded mt-1">
|
||||
<p className="text-2xs text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-100 dark:border-amber-500/30 px-2 py-1 rounded mt-1">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -37,11 +37,11 @@ export function MovementAccordion({ movements, onChange, onAdd, onRemove, onMove
|
||||
<span className="text-xs text-slate-400 mr-1">{isExpanded ? '\u25BC' : '\u25B6'}</span>
|
||||
<span className="text-sm font-medium text-slate-800">{movement.name || '(unnamed)'}</span>
|
||||
{movement.persona && (
|
||||
<span className="bg-blue-100 text-blue-700 text-xs px-2 py-0.5 rounded">
|
||||
<span className="bg-blue-100 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 text-xs px-2 py-0.5 rounded">
|
||||
{movement.persona}
|
||||
</span>
|
||||
)}
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${movement.edit ? 'bg-green-100 text-green-700' : 'bg-purple-100 text-purple-700'}`}>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${movement.edit ? 'bg-green-100 dark:bg-green-500/15 text-green-700 dark:text-green-300' : 'bg-purple-100 dark:bg-purple-500/15 text-purple-700 dark:text-purple-300'}`}>
|
||||
edit: {movement.edit ? 'on' : 'off'}
|
||||
</span>
|
||||
<span className="text-xs text-slate-400">{toolCount} tools</span>
|
||||
@ -108,7 +108,7 @@ export function MovementAccordion({ movements, onChange, onAdd, onRemove, onMove
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAdd}
|
||||
className="mt-3 text-sm text-blue-600 hover:text-blue-700"
|
||||
className="mt-3 text-sm text-blue-600 hover:text-blue-700 dark:hover:text-blue-300"
|
||||
>
|
||||
+ Add Movement
|
||||
</button>
|
||||
|
||||
@ -379,7 +379,7 @@ export function NotificationsForm() {
|
||||
<button
|
||||
onClick={() => handleDeleteRemote(s.id)}
|
||||
disabled={busy}
|
||||
className="ml-2 px-2 py-1 text-[11px] text-red-700 hover:bg-red-50 rounded"
|
||||
className="ml-2 px-2 py-1 text-[11px] text-red-700 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-500/15 rounded"
|
||||
>
|
||||
解除
|
||||
</button>
|
||||
@ -403,7 +403,7 @@ export function NotificationsForm() {
|
||||
)}
|
||||
|
||||
{pushFatal && (
|
||||
<p className="text-[12px] text-red-700">エラー: {pushFatal}</p>
|
||||
<p className="text-[12px] text-red-700 dark:text-red-300">エラー: {pushFatal}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -224,7 +224,7 @@ export function PieceEditor({ name, isAdmin = true, source }: PieceEditorProps)
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
className="px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 rounded-lg border border-red-200"
|
||||
className="px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 dark:hover:bg-red-500/15 rounded-lg border border-red-200"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@ -288,7 +288,7 @@ export function PieceEditor({ name, isAdmin = true, source }: PieceEditorProps)
|
||||
/* YAML editor */
|
||||
<div className="mb-6">
|
||||
{yamlError && (
|
||||
<div className="mb-2 px-3 py-2 bg-red-50 border border-red-200 rounded-lg text-xs text-red-600">
|
||||
<div className="mb-2 px-3 py-2 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded-lg text-xs text-red-600 dark:text-red-300">
|
||||
{yamlError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -53,7 +53,7 @@ export function ReflectionForm({ config, onChange }: SectionFormProps) {
|
||||
</div>
|
||||
|
||||
{enabled && !hasReflectionWorker && (
|
||||
<div className="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900">
|
||||
<div className="rounded-md border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 px-3 py-2 text-xs text-amber-900 dark:text-amber-300">
|
||||
<div className="font-semibold mb-1">⚠ Reflection worker が未設定です</div>
|
||||
<div>
|
||||
Reflection を有効化しても、<code className="font-mono">roles</code> に
|
||||
|
||||
@ -83,7 +83,7 @@ export function RulesTable({ rules, movementNames, onChange, disabled = false }:
|
||||
<button
|
||||
type="button"
|
||||
onClick={addRule}
|
||||
className="text-xs text-blue-600 hover:text-blue-700"
|
||||
className="text-xs text-blue-600 hover:text-blue-700 dark:hover:text-blue-300"
|
||||
>
|
||||
+ Add Rule
|
||||
</button>
|
||||
|
||||
@ -140,7 +140,7 @@ export function SecretInput({ rawValue, onChange, placeholder }: SecretInputProp
|
||||
readOnly
|
||||
className="flex-1 h-8 px-2.5 text-[13px] border border-hairline rounded-md bg-slate-50 text-slate-400 italic"
|
||||
/>
|
||||
<span className="inline-flex items-center px-2 h-6 text-2xs rounded bg-amber-50 text-amber-700 border border-amber-200">
|
||||
<span className="inline-flex items-center px-2 h-6 text-2xs rounded bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-500/30">
|
||||
will be cleared
|
||||
</span>
|
||||
</div>
|
||||
@ -180,7 +180,7 @@ export function SecretInput({ rawValue, onChange, placeholder }: SecretInputProp
|
||||
<button
|
||||
type="button"
|
||||
onClick={setClearedMode}
|
||||
className="px-2 py-0.5 rounded border border-amber-200 text-amber-700 hover:bg-amber-50"
|
||||
className="px-2 py-0.5 rounded border border-amber-200 text-amber-700 dark:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-500/15"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
|
||||
@ -29,7 +29,7 @@ function SourceBadge({ source }: { source: 'system' | 'user' }) {
|
||||
<span aria-label="locked">🔒</span> system
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-blue-50 text-blue-700">
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300">
|
||||
user
|
||||
</span>
|
||||
);
|
||||
@ -37,10 +37,10 @@ function SourceBadge({ source }: { source: 'system' | 'user' }) {
|
||||
|
||||
function SeverityBadge({ severity }: { severity: string }) {
|
||||
if (severity === 'high') {
|
||||
return <span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-red-100 text-red-700">HIGH</span>;
|
||||
return <span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-red-100 dark:bg-red-500/15 text-red-700 dark:text-red-300">HIGH</span>;
|
||||
}
|
||||
if (severity === 'medium') {
|
||||
return <span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-yellow-100 text-yellow-800">MEDIUM</span>;
|
||||
return <span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-yellow-100 dark:bg-yellow-500/15 text-yellow-800 dark:text-yellow-300">MEDIUM</span>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -178,7 +178,7 @@ export function SkillsForm() {
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="mb-3 px-3 py-2 rounded bg-red-50 border border-red-200 text-xs text-red-700">
|
||||
<div className="mb-3 px-3 py-2 rounded bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 text-xs text-red-700 dark:text-red-300">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@ -331,10 +331,10 @@ export function SkillsForm() {
|
||||
|
||||
{/* Findings */}
|
||||
{detailQuery.data.findings.length > 0 && (
|
||||
<div className="border border-yellow-200 bg-yellow-50 rounded p-2 flex flex-col gap-1">
|
||||
<div className="text-xs font-semibold text-yellow-800">Security Findings</div>
|
||||
<div className="border border-yellow-200 dark:border-yellow-500/30 bg-yellow-50 dark:bg-yellow-500/15 rounded p-2 flex flex-col gap-1">
|
||||
<div className="text-xs font-semibold text-yellow-800 dark:text-yellow-300">Security Findings</div>
|
||||
{detailQuery.data.findings.map((f, i) => (
|
||||
<div key={i} className="text-[10px] text-yellow-700 font-mono">
|
||||
<div key={i} className="text-[10px] text-yellow-700 dark:text-yellow-300 font-mono">
|
||||
<SeverityBadge severity={f.severity} />{' '}
|
||||
L{f.line}: {f.pattern} — <code>{f.match}</code>
|
||||
{f.file && <span className="text-slate-500"> ({f.file})</span>}
|
||||
@ -396,7 +396,7 @@ export function SkillsForm() {
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMut.isPending}
|
||||
className="px-3 py-1.5 text-xs font-semibold text-red-600 border border-red-200 rounded hover:bg-red-50 disabled:opacity-50 transition-colors"
|
||||
className="px-3 py-1.5 text-xs font-semibold text-red-600 border border-red-200 rounded hover:bg-red-50 dark:hover:bg-red-500/15 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{deleteMut.isPending ? 'Deleting...' : 'Delete'}
|
||||
</button>
|
||||
|
||||
@ -174,9 +174,9 @@ export function SshAuditLog() {
|
||||
function OutcomeBadge({ outcome }: { outcome: string }) {
|
||||
const colorMap: Record<string, string> = {
|
||||
pending: 'bg-slate-100 text-slate-600',
|
||||
success: 'bg-emerald-50 text-emerald-700',
|
||||
failed: 'bg-red-50 text-red-700',
|
||||
denied: 'bg-amber-50 text-amber-700',
|
||||
success: 'bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
|
||||
failed: 'bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300',
|
||||
denied: 'bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300',
|
||||
aborted: 'bg-slate-100 text-slate-500',
|
||||
};
|
||||
const cls = colorMap[outcome] ?? 'bg-slate-100 text-slate-600';
|
||||
|
||||
@ -258,7 +258,7 @@ export function SshGlobalConnectionsForm({ showToast, onChange }: Props) {
|
||||
)}
|
||||
</div>
|
||||
{c.disabledByAdminReason && (
|
||||
<div className="text-2xs text-red-700 mt-0.5">理由: {c.disabledByAdminReason}</div>
|
||||
<div className="text-2xs text-red-700 dark:text-red-300 mt-0.5">理由: {c.disabledByAdminReason}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0 flex-wrap justify-end max-w-[280px]">
|
||||
@ -371,8 +371,8 @@ function ReasonModal({ title, warning, onCancel, onSubmit }: ReasonModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div className="w-full max-w-md bg-surface rounded-md shadow-lg border border-hairline overflow-hidden">
|
||||
<div className={`px-4 py-3 border-b border-hairline ${warning ? 'bg-red-50' : ''}`}>
|
||||
<h3 className={`text-sm font-semibold ${warning ? 'text-red-800' : 'text-slate-900'}`}>{title}</h3>
|
||||
<div className={`px-4 py-3 border-b border-hairline ${warning ? 'bg-red-50 dark:bg-red-500/15' : ''}`}>
|
||||
<h3 className={`text-sm font-semibold ${warning ? 'text-red-800 dark:text-red-300' : 'text-slate-900'}`}>{title}</h3>
|
||||
</div>
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
<label className="block text-2xs font-semibold text-slate-500 uppercase tracking-wide">
|
||||
@ -404,7 +404,7 @@ function ReasonModal({ title, warning, onCancel, onSubmit }: ReasonModalProps) {
|
||||
}
|
||||
|
||||
const btnCls = 'px-2 h-7 text-2xs text-slate-700 border border-hairline rounded hover:bg-surface disabled:opacity-50';
|
||||
const btnDangerCls = 'px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 disabled:opacity-50';
|
||||
const btnDangerCls = 'px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 dark:hover:bg-red-500/15 disabled:opacity-50';
|
||||
|
||||
/**
|
||||
* Click-to-copy connection UUID. Same UX as the user folder panel — agents
|
||||
@ -436,10 +436,10 @@ function CopyableUuid({ value }: { value: string }) {
|
||||
function Badge({ color, children }: { color: 'slate' | 'blue' | 'emerald' | 'amber' | 'red'; children: React.ReactNode }) {
|
||||
const cls: Record<typeof color, string> = {
|
||||
slate: 'bg-slate-100 text-slate-600',
|
||||
blue: 'bg-blue-50 text-blue-600',
|
||||
emerald: 'bg-emerald-50 text-emerald-700',
|
||||
amber: 'bg-amber-50 text-amber-700',
|
||||
red: 'bg-red-50 text-red-700',
|
||||
blue: 'bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300',
|
||||
emerald: 'bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
|
||||
amber: 'bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300',
|
||||
red: 'bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300',
|
||||
};
|
||||
return (
|
||||
<span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${cls[color]}`}>
|
||||
|
||||
@ -105,7 +105,7 @@ export function SshGrantsForm({ showToast }: Props) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold text-slate-900">アクセス権 (grants)</h3>
|
||||
<div className="border border-amber-200 rounded-md bg-amber-50 p-4 text-xs text-amber-900">
|
||||
<div className="border border-amber-200 dark:border-amber-500/30 rounded-md bg-amber-50 dark:bg-amber-500/15 p-4 text-xs text-amber-900 dark:text-amber-300">
|
||||
<div className="font-semibold mb-1">SSH サブシステムが無効です</div>
|
||||
<div>
|
||||
<code className="font-mono">config.yaml</code> で <code className="font-mono">ssh.enabled: true</code> を設定し、
|
||||
@ -169,7 +169,7 @@ export function SshGrantsForm({ showToast }: Props) {
|
||||
<div className="text-2xs">
|
||||
<span className="font-mono font-semibold">{g.subjectType}:{g.subjectId}</span>
|
||||
{g.appliesToAllPieces ? (
|
||||
<span className="ml-2 inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-50 text-amber-700">
|
||||
<span className="ml-2 inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300">
|
||||
all pieces
|
||||
</span>
|
||||
) : (
|
||||
@ -183,7 +183,7 @@ export function SshGrantsForm({ showToast }: Props) {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setReasonForDelete(g)}
|
||||
className="px-2 h-6 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50"
|
||||
className="px-2 h-6 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 dark:hover:bg-red-500/15"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
@ -303,7 +303,7 @@ function CreateGrantForm({ connections, pieces, onSubmit, onCancel }: CreateGran
|
||||
/>
|
||||
<span>
|
||||
<span className="font-semibold">すべてのピースで利用可能 (applies_to_all_pieces)</span>
|
||||
<span className="block text-2xs text-amber-700">
|
||||
<span className="block text-2xs text-amber-700 dark:text-amber-300">
|
||||
⚠️ この grant は任意の piece からこの接続を使えるようにします。本当に必要なときのみ。
|
||||
</span>
|
||||
</span>
|
||||
@ -386,8 +386,8 @@ function ReasonModal({ title, warning, onCancel, onSubmit }: ReasonModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div className="w-full max-w-md bg-surface rounded-md shadow-lg border border-hairline overflow-hidden">
|
||||
<div className={`px-4 py-3 border-b border-hairline ${warning ? 'bg-red-50' : ''}`}>
|
||||
<h3 className={`text-sm font-semibold ${warning ? 'text-red-800' : 'text-slate-900'}`}>{title}</h3>
|
||||
<div className={`px-4 py-3 border-b border-hairline ${warning ? 'bg-red-50 dark:bg-red-500/15' : ''}`}>
|
||||
<h3 className={`text-sm font-semibold ${warning ? 'text-red-800 dark:text-red-300' : 'text-slate-900'}`}>{title}</h3>
|
||||
</div>
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
<label className="block text-2xs font-semibold text-slate-500 uppercase tracking-wide">Reason (≥ 8 chars)</label>
|
||||
|
||||
@ -99,13 +99,13 @@ export function SshMasterKeyRotationForm({ showToast }: Props) {
|
||||
{activeJobId === null && <span>idle (rotation 未実行)</span>}
|
||||
{activeJobId !== null && statusQuery.isLoading && <span className="text-slate-400">確認中…</span>}
|
||||
{activeJobId !== null && status === null && (
|
||||
<span className="text-emerald-700">job {activeJobId} は完了またはクリア済み</span>
|
||||
<span className="text-emerald-700 dark:text-emerald-300">job {activeJobId} は完了またはクリア済み</span>
|
||||
)}
|
||||
{status && (
|
||||
<>
|
||||
<span className="font-mono">{status.status}</span>
|
||||
{status.notImplemented && (
|
||||
<span className="ml-2 inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-50 text-amber-700">
|
||||
<span className="ml-2 inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300">
|
||||
stub (v1)
|
||||
</span>
|
||||
)}
|
||||
@ -169,8 +169,8 @@ function ConfirmDialog({
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div className="w-full max-w-lg bg-surface rounded-md shadow-lg border border-amber-300 overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-amber-200 bg-amber-50">
|
||||
<h3 className="text-sm font-semibold text-amber-900">⚠️ Master Key Rotation を開始</h3>
|
||||
<div className="px-4 py-3 border-b border-amber-200 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15">
|
||||
<h3 className="text-sm font-semibold text-amber-900 dark:text-amber-300">⚠️ Master Key Rotation を開始</h3>
|
||||
</div>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<p className="text-xs text-slate-700 leading-relaxed">
|
||||
|
||||
@ -221,7 +221,7 @@ function SelectedToolChip({
|
||||
// - unknown (not in catalog at all) → amber chip + "unknown" badge
|
||||
const tone =
|
||||
isUnknown || isUnavailable
|
||||
? 'bg-amber-50 text-amber-800 border border-amber-200'
|
||||
? 'bg-amber-50 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-amber-500/30'
|
||||
: 'bg-slate-100 text-slate-700';
|
||||
const tip = isUnknown
|
||||
? 'このツールは現在のカタログに存在しません。明示削除するまで保持されます。'
|
||||
@ -272,10 +272,10 @@ function Badge({
|
||||
}) {
|
||||
const cls: Record<typeof color, string> = {
|
||||
slate: 'bg-slate-100 text-slate-600',
|
||||
blue: 'bg-blue-50 text-blue-700',
|
||||
emerald: 'bg-emerald-50 text-emerald-700',
|
||||
amber: 'bg-amber-50 text-amber-700',
|
||||
red: 'bg-red-50 text-red-700',
|
||||
blue: 'bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300',
|
||||
emerald: 'bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
|
||||
amber: 'bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300',
|
||||
red: 'bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300',
|
||||
};
|
||||
return (
|
||||
<span
|
||||
|
||||
@ -218,7 +218,7 @@ export function ToolsForm({ config, onChange, visibleTabs }: ToolsFormProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-semibold text-slate-800">Knowledge (DKS)</h3>
|
||||
<span
|
||||
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wide bg-amber-100 text-amber-800 border border-amber-300"
|
||||
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-semibold uppercase tracking-wide bg-amber-100 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-500/30"
|
||||
title="この機能は legacy です。新規の知識検索統合は MCP server 経由を推奨"
|
||||
>
|
||||
LEGACY
|
||||
@ -226,14 +226,14 @@ export function ToolsForm({ config, onChange, visibleTabs }: ToolsFormProps) {
|
||||
</div>
|
||||
<div
|
||||
role="note"
|
||||
className="rounded border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-900"
|
||||
className="rounded border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 px-3 py-2 text-xs text-amber-900 dark:text-amber-300"
|
||||
>
|
||||
DKS 機能は <strong>legacy</strong> 化されており、新規の知識検索統合は{' '}
|
||||
<strong>MCP server 経由</strong> を推奨します。既存の namespace 設定は引き続き動作しますが、
|
||||
新規 namespace の追加はできません。{' '}
|
||||
<a
|
||||
href="/help"
|
||||
className="underline text-amber-900 hover:text-amber-700"
|
||||
className="underline text-amber-900 dark:text-amber-300 hover:text-amber-700 dark:hover:text-amber-300"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export function EnvOverrideWarning() {
|
||||
return (
|
||||
<div className="text-2xs text-amber-700 bg-amber-50 border border-amber-100 px-2 py-1 rounded mt-1">
|
||||
<div className="text-2xs text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-100 dark:border-amber-500/30 px-2 py-1 rounded mt-1">
|
||||
環境変数で上書きされています(保存しても反映されません)
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -43,7 +43,7 @@ export function EmptyState({ title, description, hint, compact, action, onCreate
|
||||
'コメントを送ると追加指示として処理される',
|
||||
].map((step, i) => (
|
||||
<li key={i} className="flex gap-3 items-start text-xs text-slate-500">
|
||||
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-blue-100 text-blue-700 text-[10px] font-bold flex items-center justify-center mt-0.5">
|
||||
<span className="flex-shrink-0 w-5 h-5 rounded-full bg-blue-100 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 text-[10px] font-bold flex items-center justify-center mt-0.5">
|
||||
{i + 1}
|
||||
</span>
|
||||
{step}
|
||||
|
||||
@ -78,7 +78,7 @@ export function AgentsMdPanel({ onDirtyChange }: AgentsMdPanelProps) {
|
||||
{data?.exists && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-2xs text-red-600 hover:text-red-800 underline"
|
||||
className="text-2xs text-red-600 hover:text-red-800 dark:hover:text-red-300 underline"
|
||||
onClick={() => {
|
||||
if (window.confirm('AGENTS.md を削除しますか?')) del.mutate();
|
||||
}}
|
||||
|
||||
@ -9,10 +9,10 @@ import { AddBrowserSessionDialog } from './AddBrowserSessionDialog';
|
||||
function StatusPill({ status }: { status: BrowserSessionProfile['status'] }) {
|
||||
const map: Record<BrowserSessionProfile['status'], string> = {
|
||||
pending: 'bg-slate-200 text-slate-700',
|
||||
active: 'bg-emerald-100 text-emerald-700',
|
||||
expired: 'bg-amber-100 text-amber-800',
|
||||
active: 'bg-emerald-100 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
|
||||
expired: 'bg-amber-100 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300',
|
||||
revoked: 'bg-slate-200 text-slate-500',
|
||||
error: 'bg-rose-100 text-rose-700',
|
||||
error: 'bg-rose-100 dark:bg-rose-500/15 text-rose-700 dark:text-rose-300',
|
||||
};
|
||||
const labels: Record<BrowserSessionProfile['status'], string> = {
|
||||
pending: '保留中',
|
||||
@ -87,7 +87,7 @@ export function BrowserSessionsPanel() {
|
||||
<button onClick={() => { setReLoginProfileId(p.id); setAdding(true); }}
|
||||
className="text-xs text-slate-700 hover:text-slate-900 px-2 py-1 rounded hover:bg-surface">再ログイン</button>
|
||||
<button onClick={() => { if (confirm(`${p.label} を削除しますか?`)) del.mutate(p.id); }}
|
||||
className="text-xs text-rose-600 hover:text-rose-800 px-2 py-1 rounded hover:bg-rose-50">削除</button>
|
||||
className="text-xs text-rose-600 hover:text-rose-800 dark:hover:text-rose-300 px-2 py-1 rounded hover:bg-rose-50 dark:hover:bg-rose-500/15">削除</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -132,7 +132,7 @@ export function FileTree({
|
||||
e.stopPropagation();
|
||||
onDeleteFile(subdir, file.name);
|
||||
}}
|
||||
className={`flex-shrink-0 w-4 h-4 flex items-center justify-center rounded hover:bg-red-100 hover:text-red-600 transition-colors ${
|
||||
className={`flex-shrink-0 w-4 h-4 flex items-center justify-center rounded hover:bg-red-100 dark:hover:bg-red-500/15 hover:text-red-600 transition-colors ${
|
||||
isSelected ? 'text-accent-fg/70' : 'text-slate-400'
|
||||
}`}
|
||||
>
|
||||
|
||||
@ -36,7 +36,7 @@ function OwnerBadge({ ownerId }: { ownerId: string | null }) {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 text-blue-600 leading-none">
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300 leading-none">
|
||||
personal
|
||||
</span>
|
||||
);
|
||||
|
||||
@ -233,7 +233,7 @@ function ScopeBadge({ ownerId }: { ownerId: string | null }) {
|
||||
return ownerId === null ? (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-slate-100 text-slate-500 leading-none">global</span>
|
||||
) : (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 text-blue-600 leading-none">personal</span>
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300 leading-none">personal</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -341,10 +341,10 @@ export function McpPanel({ showToast }: { showToast?: ShowToast }) {
|
||||
<span className="text-[13px] font-medium text-slate-900">{s.name}</span>
|
||||
<ScopeBadge ownerId={s.ownerId} />
|
||||
<span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${
|
||||
s.authKind === 'oauth' ? 'bg-purple-50 text-purple-600' : 'bg-amber-50 text-amber-600'
|
||||
s.authKind === 'oauth' ? 'bg-purple-50 dark:bg-purple-500/15 text-purple-600 dark:text-purple-300' : 'bg-amber-50 dark:bg-amber-500/15 text-amber-600 dark:text-amber-300'
|
||||
}`}>{s.authKind === 'oauth' ? 'OAuth' : 'API key'}</span>
|
||||
{s.toolCount != null && s.toolCount > 0 && (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-50 text-green-700 leading-none">
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-50 dark:bg-green-500/15 text-green-700 dark:text-green-300 leading-none">
|
||||
{s.toolCount} ツール
|
||||
</span>
|
||||
)}
|
||||
@ -372,7 +372,7 @@ export function McpPanel({ showToast }: { showToast?: ShowToast }) {
|
||||
onClick={() => setEditingId(s.id)}>
|
||||
編集
|
||||
</button>
|
||||
<button type="button" className="text-2xs text-red-600 hover:text-red-800 underline"
|
||||
<button type="button" className="text-2xs text-red-600 hover:text-red-800 dark:hover:text-red-300 underline"
|
||||
onClick={() => handleDelete(s.id, s.name, isGlobal)}
|
||||
disabled={delMut.isPending}>
|
||||
削除
|
||||
|
||||
@ -311,8 +311,8 @@ function ServerTable({
|
||||
<td className="py-2 pr-2">
|
||||
<span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${
|
||||
s.authKind === 'oauth'
|
||||
? 'bg-purple-50 text-purple-600'
|
||||
: 'bg-amber-50 text-amber-600'
|
||||
? 'bg-purple-50 dark:bg-purple-500/15 text-purple-600 dark:text-purple-300'
|
||||
: 'bg-amber-50 dark:bg-amber-500/15 text-amber-600 dark:text-amber-300'
|
||||
}`}>
|
||||
{s.authKind === 'oauth' ? 'OAuth' : 'API key'}
|
||||
</span>
|
||||
@ -322,7 +322,7 @@ function ServerTable({
|
||||
{s.toolCount == null || s.toolCount === 0 ? (
|
||||
<span className="text-[10px] text-slate-400 italic">未取得 — ツール更新を押してください</span>
|
||||
) : (
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-50 text-green-700 leading-none">
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-green-50 dark:bg-green-500/15 text-green-700 dark:text-green-300 leading-none">
|
||||
{s.toolCount} ツール
|
||||
</span>
|
||||
)}
|
||||
@ -337,7 +337,7 @@ function ServerTable({
|
||||
{canDelete && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-2xs text-red-600 hover:text-red-800 underline"
|
||||
className="text-2xs text-red-600 hover:text-red-800 dark:hover:text-red-300 underline"
|
||||
onClick={() => onDelete(s.id, s.name, isGlobal)}
|
||||
disabled={deletePending}
|
||||
>削除</button>
|
||||
@ -484,7 +484,7 @@ export function McpServersPanel({ showToast }: McpServersPanelProps = {}) {
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-[13px] font-semibold text-slate-900">個人サーバー</h3>
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 text-blue-600 leading-none">
|
||||
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-medium bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300 leading-none">
|
||||
personal
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -104,7 +104,7 @@ export function MonacoFileEditor({ subdir, filename, content, mtime, size, onSav
|
||||
{filename}
|
||||
</span>
|
||||
{dirty && (
|
||||
<span className="text-[10px] font-medium text-amber-600 bg-amber-50 border border-amber-200 rounded px-1.5 py-0.5">
|
||||
<span className="text-[10px] font-medium text-amber-600 dark:text-amber-300 bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 rounded px-1.5 py-0.5">
|
||||
unsaved
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -393,7 +393,7 @@ export function PetsPanel({ showToast }: { showToast?: ShowToast }) {
|
||||
<span className="inline-block w-3 text-slate-400">{collapsed ? '▶' : '▼'}</span>
|
||||
{' '}
|
||||
{worker.id}
|
||||
<span className="ml-2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-violet-50 text-violet-700">
|
||||
<span className="ml-2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-violet-50 dark:bg-violet-500/15 text-violet-700 dark:text-violet-300">
|
||||
proxy
|
||||
</span>
|
||||
</div>
|
||||
@ -461,7 +461,7 @@ export function PetsPanel({ showToast }: { showToast?: ShowToast }) {
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] font-semibold text-slate-900 truncate">{pet.name}</span>
|
||||
{active && (
|
||||
<span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-emerald-50 text-emerald-700">
|
||||
<span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300">
|
||||
default
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -274,7 +274,7 @@ export function SaveAsScriptDialog({ recordingName, onClose, onSuccess }: SaveAs
|
||||
type="button"
|
||||
onClick={() => removeHint(idx)}
|
||||
aria-label="Remove hint"
|
||||
className="mt-1 w-5 h-5 flex-shrink-0 flex items-center justify-center rounded hover:bg-red-100 hover:text-red-600 text-slate-400 transition-colors"
|
||||
className="mt-1 w-5 h-5 flex-shrink-0 flex items-center justify-center rounded hover:bg-red-100 dark:hover:bg-red-500/15 hover:text-red-600 text-slate-400 transition-colors"
|
||||
>
|
||||
<svg viewBox="0 0 16 16" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
||||
<path d="M4 4l8 8M12 4l-8 8" />
|
||||
@ -297,7 +297,7 @@ export function SaveAsScriptDialog({ recordingName, onClose, onSuccess }: SaveAs
|
||||
|
||||
{/* Errors */}
|
||||
{(validationError || conflictError || submitError) && (
|
||||
<div className="px-3 py-2 rounded-md bg-red-50 border border-red-200 text-xs text-red-700">
|
||||
<div className="px-3 py-2 rounded-md bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 text-xs text-red-700 dark:text-red-300">
|
||||
{validationError ?? conflictError ?? submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -324,8 +324,8 @@ export function SshConnectionForm({ existing, adminContext, onSubmit, onCancel }
|
||||
</div>
|
||||
|
||||
{adminContext && (
|
||||
<fieldset className="rounded border border-amber-200 bg-amber-50/50 p-3">
|
||||
<legend className="px-1 text-2xs font-semibold text-amber-800 uppercase tracking-wide">
|
||||
<fieldset className="rounded border border-amber-200 dark:border-amber-500/30 bg-amber-50/50 dark:bg-amber-500/15 p-3">
|
||||
<legend className="px-1 text-2xs font-semibold text-amber-800 dark:text-amber-300 uppercase tracking-wide">
|
||||
Admin-only flags
|
||||
</legend>
|
||||
<label className="flex items-start gap-2 text-xs cursor-pointer">
|
||||
|
||||
@ -408,7 +408,7 @@ function ConnectionRow(props: ConnectionRowProps) {
|
||||
)}
|
||||
</div>
|
||||
{c.disabledByAdminReason && (
|
||||
<div className="text-2xs text-red-700 mt-0.5">理由: {c.disabledByAdminReason}</div>
|
||||
<div className="text-2xs text-red-700 dark:text-red-300 mt-0.5">理由: {c.disabledByAdminReason}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0 flex-wrap justify-end">
|
||||
@ -442,7 +442,7 @@ function ConnectionRow(props: ConnectionRowProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
className="px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50"
|
||||
className="px-2 h-7 text-2xs text-red-600 border border-hairline rounded hover:bg-red-50 dark:hover:bg-red-500/15"
|
||||
>
|
||||
削除
|
||||
</button>
|
||||
@ -508,10 +508,10 @@ function HostKeyBadge({ verified, pending }: { verified: boolean; pending: boole
|
||||
function Badge({ color, children }: { color: 'slate' | 'blue' | 'emerald' | 'amber' | 'red'; children: React.ReactNode }) {
|
||||
const cls: Record<typeof color, string> = {
|
||||
slate: 'bg-slate-100 text-slate-600',
|
||||
blue: 'bg-blue-50 text-blue-600',
|
||||
emerald: 'bg-emerald-50 text-emerald-700',
|
||||
amber: 'bg-amber-50 text-amber-700',
|
||||
red: 'bg-red-50 text-red-700',
|
||||
blue: 'bg-blue-50 dark:bg-blue-500/15 text-blue-600 dark:text-blue-300',
|
||||
emerald: 'bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300',
|
||||
amber: 'bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300',
|
||||
red: 'bg-red-50 dark:bg-red-500/15 text-red-700 dark:text-red-300',
|
||||
};
|
||||
return (
|
||||
<span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-medium leading-none ${cls[color]}`}>
|
||||
|
||||
@ -39,7 +39,7 @@ export function SshPublicKeyDialog({ publicKey, label, freshlyGenerated, onClose
|
||||
公開鍵 {label && <span className="font-normal text-slate-500">— {label}</span>}
|
||||
</h3>
|
||||
{freshlyGenerated && (
|
||||
<p className="text-2xs text-emerald-700 mt-1">
|
||||
<p className="text-2xs text-emerald-700 dark:text-emerald-300 mt-1">
|
||||
新しい鍵を Orchestrator で生成しました。下の公開鍵を接続先の
|
||||
<code className="font-mono"> ~/.ssh/authorized_keys</code> に追加してください。
|
||||
</p>
|
||||
|
||||
@ -381,7 +381,7 @@ export function SubscriptionsPanel({ currentUserId }: { currentUserId: string })
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="text-2xs text-red-600 hover:text-red-800 font-medium px-2 py-0.5 rounded hover:bg-red-50 transition-colors"
|
||||
className="text-2xs text-red-600 hover:text-red-800 dark:hover:text-red-300 font-medium px-2 py-0.5 rounded hover:bg-red-50 dark:hover:bg-red-500/15 transition-colors"
|
||||
onClick={() =>
|
||||
unsubscribe.mutate({ publisher: s.publisher_user_id, folder: s.folder })
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@
|
||||
@apply bg-canvas text-slate-700 border-hairline hover:bg-surface;
|
||||
}
|
||||
.btn-danger {
|
||||
@apply bg-canvas text-red-700 border-red-200 hover:bg-red-50;
|
||||
@apply bg-canvas text-red-700 border-red-200 hover:bg-red-50 dark:hover:bg-red-500/15;
|
||||
}
|
||||
|
||||
.chat-pet-overlay {
|
||||
|
||||
26
ui/src/lib/utils.test.ts
Normal file
26
ui/src/lib/utils.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { statusTone, toneClasses } from './utils';
|
||||
|
||||
describe('statusTone', () => {
|
||||
it('returns theme-adaptive light-dark() colors', () => {
|
||||
const t = statusTone('failed');
|
||||
expect(t.bg).toBe('light-dark(#fee2e2, rgba(239,68,68,.15))');
|
||||
expect(t.fg).toBe('light-dark(#b91c1c, #fca5a5)');
|
||||
});
|
||||
it('falls back to neutral surface/muted vars', () => {
|
||||
const t = statusTone('whatever-unknown');
|
||||
expect(t.bg).toBe('light-dark(#e2e8f0, var(--surface-2))');
|
||||
expect(t.fg).toBe('light-dark(#475569, var(--muted))');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toneClasses', () => {
|
||||
it('pairs light + dark for colored tones', () => {
|
||||
expect(toneClasses('danger')).toContain('bg-red-50');
|
||||
expect(toneClasses('danger')).toContain('dark:text-red-300');
|
||||
expect(toneClasses('success')).toContain('dark:bg-emerald-500/15');
|
||||
});
|
||||
it('neutral stays on semantic tokens (auto-themed, no dark: needed)', () => {
|
||||
expect(toneClasses('neutral')).toBe('bg-surface-2 text-slate-700 border-hairline');
|
||||
});
|
||||
});
|
||||
@ -39,14 +39,40 @@ export function stateTone(state: string): { bg: string; fg: string } {
|
||||
return { bg: '#dbeafe', fg: '#1e3a8a' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Status badge colors. Returned values are CSS `light-dark()` pairs so the
|
||||
* inline-styled badges (style={{ background, color }}) adapt to the theme —
|
||||
* `color-scheme` is set on :root / [data-theme=dark] (see index.css), which is
|
||||
* what light-dark() resolves against. Dark = a subtle tinted chip + light text.
|
||||
*/
|
||||
export function statusTone(status: string): { bg: string; fg: string } {
|
||||
if (status === 'running') return { bg: '#dcfce7', fg: '#166534' };
|
||||
if (status === 'waiting_human') return { bg: '#fef9c3', fg: '#854d0e' };
|
||||
if (status === 'waiting_subtasks') return { bg: '#e0e7ff', fg: '#3730a3' };
|
||||
if (status === 'failed') return { bg: '#fee2e2', fg: '#b91c1c' };
|
||||
if (status === 'succeeded') return { bg: '#dbeafe', fg: '#1e40af' };
|
||||
if (status === 'retry') return { bg: '#fef3c7', fg: '#92400e' };
|
||||
return { bg: '#e2e8f0', fg: '#475569' };
|
||||
const ld = (l: string, d: string) => `light-dark(${l}, ${d})`;
|
||||
if (status === 'running') return { bg: ld('#dcfce7', 'rgba(34,197,94,.15)'), fg: ld('#166534', '#86efac') };
|
||||
if (status === 'waiting_human') return { bg: ld('#fef9c3', 'rgba(234,179,8,.15)'), fg: ld('#854d0e', '#fde047') };
|
||||
if (status === 'waiting_subtasks') return { bg: ld('#e0e7ff', 'rgba(99,102,241,.18)'), fg: ld('#3730a3', '#a5b4fc') };
|
||||
if (status === 'failed') return { bg: ld('#fee2e2', 'rgba(239,68,68,.15)'), fg: ld('#b91c1c', '#fca5a5') };
|
||||
if (status === 'succeeded') return { bg: ld('#dbeafe', 'rgba(59,130,246,.18)'), fg: ld('#1e40af', '#93c5fd') };
|
||||
if (status === 'retry') return { bg: ld('#fef3c7', 'rgba(245,158,11,.15)'), fg: ld('#92400e', '#fcd34d') };
|
||||
return { bg: ld('#e2e8f0', 'var(--surface-2)'), fg: ld('#475569', 'var(--muted)') };
|
||||
}
|
||||
|
||||
export type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
||||
|
||||
/**
|
||||
* Shared light+dark Tailwind classes for semantic badge/chip tones. One source
|
||||
* of truth so badges stay consistent in both themes. `dark:` follows the
|
||||
* in-app [data-theme] (tailwind darkMode is the selector variant).
|
||||
*/
|
||||
const TONE_CLASSES: Record<Tone, string> = {
|
||||
neutral: 'bg-surface-2 text-slate-700 border-hairline',
|
||||
info: 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-500/15 dark:text-blue-300 dark:border-blue-500/30',
|
||||
success: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-300 dark:border-emerald-500/30',
|
||||
warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-500/15 dark:text-amber-300 dark:border-amber-500/30',
|
||||
danger: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-500/15 dark:text-red-300 dark:border-red-500/30',
|
||||
};
|
||||
|
||||
export function toneClasses(tone: Tone): string {
|
||||
return TONE_CLASSES[tone];
|
||||
}
|
||||
|
||||
export function formatStatusLabel(status: string): string {
|
||||
|
||||
@ -76,7 +76,7 @@ export function AdminCaptchaPage({ isAdmin }: { isAdmin: boolean }) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center p-8">
|
||||
<div className="max-w-md text-center text-sm text-red-700">
|
||||
<div className="max-w-md text-center text-sm text-red-700 dark:text-red-300">
|
||||
CAPTCHA Pool の取得に失敗しました: {msg}
|
||||
</div>
|
||||
</div>
|
||||
@ -90,7 +90,7 @@ export function AdminCaptchaPage({ isAdmin }: { isAdmin: boolean }) {
|
||||
<h1 className="text-base font-semibold text-slate-900">CAPTCHA Pool</h1>
|
||||
<span className="text-2xs font-mono text-slate-400">admin</span>
|
||||
{data?.available && data.captchaPending && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-2xs font-medium border border-amber-300 bg-amber-50 text-amber-800 animate-pulse">
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-2xs font-medium border border-amber-300 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300 animate-pulse">
|
||||
⚠ CAPTCHA 待ち
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -38,7 +38,7 @@ function DriftBadge({ drift }: { drift: DriftStatus }) {
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); setOpen(p => !p); }}
|
||||
title="組み込み Piece がフォーク後に更新されました"
|
||||
className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-amber-100 text-amber-800 hover:bg-amber-200 transition-colors leading-none border border-amber-300"
|
||||
className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-amber-100 dark:bg-amber-500/15 text-amber-800 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-500/25 transition-colors leading-none border border-amber-300 dark:border-amber-500/30"
|
||||
>
|
||||
updated
|
||||
</button>
|
||||
@ -55,7 +55,7 @@ function DriftBadge({ drift }: { drift: DriftStatus }) {
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-slate-500">現在の組み込み</span>
|
||||
<code className="font-mono text-[10px] bg-amber-50 px-1 rounded text-amber-800">
|
||||
<code className="font-mono text-[10px] bg-amber-50 dark:bg-amber-500/15 px-1 rounded text-amber-800 dark:text-amber-300">
|
||||
{shortSha(drift.latestCommit)}
|
||||
</code>
|
||||
</div>
|
||||
@ -308,7 +308,7 @@ function PiecesSidebar({
|
||||
className="w-full h-8 px-2 text-xs border border-hairline rounded-md focus:outline-none focus:ring-2 focus:ring-accent-ring focus:border-accent"
|
||||
/>
|
||||
{duplicateError && (
|
||||
<div role="alert" className="mt-2 text-2xs text-red-700">{duplicateError}</div>
|
||||
<div role="alert" className="mt-2 text-2xs text-red-700 dark:text-red-300">{duplicateError}</div>
|
||||
)}
|
||||
<div className="mt-3 flex justify-end gap-2">
|
||||
<button
|
||||
|
||||
@ -521,15 +521,15 @@ function ScheduleListItem({ task, active, onClick }: { task: ScheduledTask; acti
|
||||
<span className="text-slate-400">system</span>
|
||||
)}
|
||||
{task.visibility === 'private' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-amber-50 text-amber-700 border border-amber-100" title="非公開">🔒 非公開</span>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300 border border-amber-100 dark:border-amber-500/30" title="非公開">🔒 非公開</span>
|
||||
)}
|
||||
{task.visibility === 'org' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-blue-50 text-blue-700 border border-blue-100" title={`${task.visibilityScopeOrgName ?? 'org'} と共有`}>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-blue-50 dark:bg-blue-500/15 text-blue-700 dark:text-blue-300 border border-blue-100 dark:border-blue-500/30" title={`${task.visibilityScopeOrgName ?? 'org'} と共有`}>
|
||||
🏢 {task.visibilityScopeOrgName ?? 'org'}
|
||||
</span>
|
||||
)}
|
||||
{task.visibility === 'public' && (
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-emerald-50 text-emerald-700 border border-emerald-100" title="公開">🌐 公開</span>
|
||||
<span className="px-1 rounded text-[10px] font-medium bg-emerald-50 dark:bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border border-emerald-100 dark:border-emerald-500/30" title="公開">🌐 公開</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -665,7 +665,7 @@ function ScheduleDetailPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDelete(task.id)}
|
||||
className="px-2.5 sm:px-3 h-7 sm:h-8 rounded-md text-xs font-medium bg-canvas border border-red-200 text-red-700 hover:bg-red-50 active:scale-[0.97] active:bg-red-50 transition-[transform,background-color,color] duration-100 whitespace-nowrap"
|
||||
className="px-2.5 sm:px-3 h-7 sm:h-8 rounded-md text-xs font-medium bg-canvas border border-red-200 text-red-700 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-500/15 active:scale-[0.97] active:bg-red-50 dark:active:bg-red-500/15 transition-[transform,background-color,color] duration-100 whitespace-nowrap"
|
||||
>
|
||||
削除
|
||||
</button>
|
||||
@ -1210,7 +1210,7 @@ function ScheduleEditor({ mode, initialTask, onCancel, onSaved }: ScheduleEditor
|
||||
<div className="text-2xs text-slate-500 mt-1">→ {orgs[0].orgName}</div>
|
||||
)}
|
||||
{form.visibility === 'org' && orgs.length === 0 && (
|
||||
<div className="text-2xs text-amber-700 mt-1">
|
||||
<div className="text-2xs text-amber-700 dark:text-amber-300 mt-1">
|
||||
Organization に所属していません。Private または Public を選択してください。
|
||||
</div>
|
||||
)}
|
||||
@ -1247,7 +1247,7 @@ function ScheduleEditor({ mode, initialTask, onCancel, onSaved }: ScheduleEditor
|
||||
{error && (
|
||||
<div
|
||||
role="alert"
|
||||
className="px-3.5 py-2.5 bg-red-50 border border-red-200 rounded-md text-xs text-red-700"
|
||||
className="px-3.5 py-2.5 bg-red-50 dark:bg-red-500/15 border border-red-200 dark:border-red-500/30 rounded-md text-xs text-red-700 dark:text-red-300"
|
||||
>
|
||||
⚠ {error}
|
||||
</div>
|
||||
|
||||
@ -22,16 +22,16 @@ interface UserRecord {
|
||||
type UserFilter = 'all' | 'admin' | 'user' | 'pending';
|
||||
|
||||
const STATUS_TONE: Record<UserRecord['status'], { bg: string; fg: string; border?: string; label: string }> = {
|
||||
pending: { bg: 'bg-amber-50', fg: 'text-amber-700', border: 'border-amber-100', label: '承認待ち' },
|
||||
active: { bg: 'bg-emerald-50', fg: 'text-emerald-700', border: 'border-emerald-100', label: 'アクティブ' },
|
||||
pending: { bg: 'bg-amber-50 dark:bg-amber-500/15', fg: 'text-amber-700 dark:text-amber-300', border: 'border-amber-100 dark:border-amber-500/30', label: '承認待ち' },
|
||||
active: { bg: 'bg-emerald-50 dark:bg-emerald-500/15', fg: 'text-emerald-700 dark:text-emerald-300', border: 'border-emerald-100 dark:border-emerald-500/30', label: 'アクティブ' },
|
||||
disabled: { bg: 'bg-surface-2', fg: 'text-slate-600', border: 'border-hairline', label: '無効' },
|
||||
};
|
||||
|
||||
const ROLE_TONE: Record<UserRecord['role'], { bg: string; fg: string; border?: string; label: string; desc: string }> = {
|
||||
admin: {
|
||||
bg: 'bg-blue-50',
|
||||
fg: 'text-blue-700',
|
||||
border: 'border-blue-100',
|
||||
bg: 'bg-blue-50 dark:bg-blue-500/15',
|
||||
fg: 'text-blue-700 dark:text-blue-300',
|
||||
border: 'border-blue-100 dark:border-blue-500/30',
|
||||
label: 'Admin',
|
||||
desc: '全ての設定変更・ユーザー管理・システム操作',
|
||||
},
|
||||
@ -347,7 +347,7 @@ function UserListItem({ user, active, onClick }: { user: UserRecord; active: boo
|
||||
{user.name || '(未設定)'}
|
||||
</span>
|
||||
{user.status === 'pending' && (
|
||||
<span className="text-[10px] font-medium px-1 rounded bg-amber-50 text-amber-700 border border-amber-100 flex-shrink-0">承認待ち</span>
|
||||
<span className="text-[10px] font-medium px-1 rounded bg-amber-50 dark:bg-amber-500/15 text-amber-700 dark:text-amber-300 border border-amber-100 dark:border-amber-500/30 flex-shrink-0">承認待ち</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-2xs text-slate-500 truncate">{user.email}</div>
|
||||
@ -427,7 +427,7 @@ function UserDetailPane({ user, onPatch, onDelete, onMobileBack }: UserDetailPan
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onPatch(user.id, { status: 'disabled' })}
|
||||
className="px-3 h-7 rounded-md text-xs font-medium bg-canvas border border-amber-200 text-amber-700 hover:bg-amber-50 transition-colors whitespace-nowrap"
|
||||
className="px-3 h-7 rounded-md text-xs font-medium bg-canvas border border-amber-200 text-amber-700 dark:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-500/15 transition-colors whitespace-nowrap"
|
||||
>
|
||||
無効化
|
||||
</button>
|
||||
@ -444,7 +444,7 @@ function UserDetailPane({ user, onPatch, onDelete, onMobileBack }: UserDetailPan
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDelete(user.id)}
|
||||
className="px-3 h-7 rounded-md text-xs font-medium bg-canvas border border-red-200 text-red-700 hover:bg-red-50 transition-colors whitespace-nowrap"
|
||||
className="px-3 h-7 rounded-md text-xs font-medium bg-canvas border border-red-200 text-red-700 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-500/15 transition-colors whitespace-nowrap"
|
||||
>
|
||||
削除
|
||||
</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user