Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
212 lines
6.3 KiB
TypeScript
212 lines
6.3 KiB
TypeScript
import { ChevronRight } from 'lucide-react';
|
|
import React from 'react';
|
|
import { useI18n } from '../../../application/i18n/I18nProvider';
|
|
import { cn } from '../../../lib/utils';
|
|
import type { VaultToolArtifact } from './vaultToolArtifact';
|
|
import {
|
|
canNavigateVaultArtifact,
|
|
navigateVaultArtifact,
|
|
useVaultArtifactNavigation,
|
|
} from './VaultArtifactNavigationContext';
|
|
import { VaultArtifactIcon } from './vaultArtifactPresentation';
|
|
|
|
interface VaultArtifactCardProps {
|
|
artifact: VaultToolArtifact;
|
|
toolName?: string;
|
|
className?: string;
|
|
}
|
|
|
|
function getArtifactPresentation(
|
|
artifact: VaultToolArtifact,
|
|
t: (key: string, values?: Record<string, unknown>) => string,
|
|
): {
|
|
title: string;
|
|
subtitle?: string;
|
|
clickable: boolean;
|
|
} {
|
|
switch (artifact.kind) {
|
|
case 'vault.note':
|
|
return {
|
|
title: artifact.title,
|
|
subtitle: artifact.group ?? t('ai.chat.artifact.noteFallback'),
|
|
clickable: true,
|
|
};
|
|
case 'vault.host':
|
|
return {
|
|
title: artifact.label,
|
|
subtitle: artifact.port
|
|
? `${artifact.hostname}:${artifact.port}`
|
|
: artifact.hostname,
|
|
clickable: true,
|
|
};
|
|
case 'vault.hosts.batch': {
|
|
const preview = artifact.preview
|
|
.slice(0, 2)
|
|
.map((host) => host.label || host.hostname)
|
|
.filter(Boolean)
|
|
.join(', ');
|
|
return {
|
|
title: artifact.dryRun
|
|
? t('ai.chat.artifact.hostsPreview', { count: artifact.addedCount })
|
|
: t('ai.chat.artifact.hostsAdded', { count: artifact.addedCount }),
|
|
subtitle: preview || t('ai.chat.artifact.openHosts'),
|
|
clickable: true,
|
|
};
|
|
}
|
|
case 'vault.summary':
|
|
return {
|
|
title: artifact.section === 'notes'
|
|
? t('ai.chat.artifact.notesSummary', { count: artifact.count })
|
|
: artifact.section === 'hosts'
|
|
? t('ai.chat.artifact.hostsSummary', { count: artifact.count })
|
|
: artifact.section === 'snippets'
|
|
? t('ai.chat.artifact.snippetsSummary', { count: artifact.count })
|
|
: t('ai.chat.artifact.scriptsSummary', { count: artifact.count }),
|
|
subtitle: artifact.section === 'notes'
|
|
? t('ai.chat.artifact.openNotes')
|
|
: artifact.section === 'hosts'
|
|
? t('ai.chat.artifact.openHosts')
|
|
: t('ai.chat.artifact.openSnippets'),
|
|
clickable: true,
|
|
};
|
|
case 'vault.snippet':
|
|
return {
|
|
title: artifact.label,
|
|
subtitle: artifact.package || t('ai.chat.artifact.snippetFallback'),
|
|
clickable: true,
|
|
};
|
|
case 'vault.script':
|
|
return {
|
|
title: artifact.label,
|
|
subtitle: artifact.language
|
|
? t('ai.chat.artifact.scriptLanguage', { language: artifact.language })
|
|
: artifact.package || t('ai.chat.artifact.scriptFallback'),
|
|
clickable: true,
|
|
};
|
|
case 'vault.snippet.deleted':
|
|
return {
|
|
title: t('ai.chat.artifact.snippetDeleted'),
|
|
subtitle: artifact.snippetId,
|
|
clickable: false,
|
|
};
|
|
case 'vault.script.deleted':
|
|
return {
|
|
title: t('ai.chat.artifact.scriptDeleted'),
|
|
subtitle: artifact.scriptId,
|
|
clickable: false,
|
|
};
|
|
case 'vault.snippet.run':
|
|
return {
|
|
title: t('ai.chat.artifact.snippetRan'),
|
|
subtitle: artifact.command || artifact.snippetId,
|
|
clickable: true,
|
|
};
|
|
case 'vault.script.run':
|
|
return {
|
|
title: artifact.status
|
|
? t('ai.chat.artifact.scriptRunStatus', { status: artifact.status })
|
|
: t('ai.chat.artifact.scriptStarted'),
|
|
subtitle: artifact.runId,
|
|
clickable: true,
|
|
};
|
|
case 'vault.script.runs':
|
|
return {
|
|
title: t('ai.chat.artifact.scriptRunsSummary', { count: artifact.count }),
|
|
subtitle: t('ai.chat.artifact.openSnippets'),
|
|
clickable: true,
|
|
};
|
|
case 'vault.script.action':
|
|
return {
|
|
title: artifact.action === 'stop'
|
|
? t('ai.chat.artifact.scriptRunStopped')
|
|
: artifact.action === 'pause'
|
|
? t('ai.chat.artifact.scriptRunPaused')
|
|
: t('ai.chat.artifact.scriptRunResumed'),
|
|
subtitle: artifact.runId,
|
|
clickable: false,
|
|
};
|
|
case 'vault.script.reference':
|
|
return {
|
|
title: t('ai.chat.artifact.scriptReference'),
|
|
subtitle: t('ai.chat.artifact.openSnippets'),
|
|
clickable: true,
|
|
};
|
|
case 'error':
|
|
return {
|
|
title: t('ai.chat.artifact.failed'),
|
|
subtitle: artifact.message,
|
|
clickable: false,
|
|
};
|
|
default:
|
|
return {
|
|
title: '',
|
|
clickable: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
export const VaultArtifactCard: React.FC<VaultArtifactCardProps> = ({
|
|
artifact,
|
|
toolName,
|
|
className,
|
|
}) => {
|
|
const { t } = useI18n();
|
|
const navigation = useVaultArtifactNavigation();
|
|
const presentation = getArtifactPresentation(artifact, t);
|
|
const canNavigate = presentation.clickable && canNavigateVaultArtifact(artifact, navigation);
|
|
|
|
const handleClick = () => {
|
|
if (!canNavigate || !navigation) return;
|
|
navigateVaultArtifact(artifact, navigation);
|
|
};
|
|
|
|
const content = (
|
|
<>
|
|
<VaultArtifactIcon artifact={artifact} toolName={toolName} />
|
|
<div className="min-w-0 flex-1 text-left">
|
|
<div className="truncate text-[12px] font-medium text-foreground/85">
|
|
{presentation.title}
|
|
</div>
|
|
{presentation.subtitle && (
|
|
<div className="truncate text-[11px] text-muted-foreground/60">
|
|
{presentation.subtitle}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{canNavigate && (
|
|
<ChevronRight size={12} className="shrink-0 text-muted-foreground/40" />
|
|
)}
|
|
</>
|
|
);
|
|
|
|
if (!canNavigate) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
presentation.clickable
|
|
? 'flex w-full items-center gap-2.5 rounded-md border border-border/25 bg-muted/10 px-2.5 py-2 text-left'
|
|
: 'flex items-center gap-2 px-1 py-0.5',
|
|
className,
|
|
'cursor-default',
|
|
)}
|
|
>
|
|
{content}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={handleClick}
|
|
className={cn(
|
|
'flex w-full items-center gap-2.5 rounded-md border border-border/25 bg-muted/10 px-2.5 py-2',
|
|
'text-left transition-colors hover:bg-muted/20',
|
|
className,
|
|
)}
|
|
>
|
|
{content}
|
|
</button>
|
|
);
|
|
};
|