mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-29 07:58:47 +02:00
feat: phase 3 frontend — model badges, argument-hint composer, ancestor path display
Surface prompt model and argumentHint metadata across the UI: - InlinePromptPill: send model in promptInvocation, show model badge in dropdown, show argumentHint preview, support armed-prompt state for prompts with argumentHint (prompt stays pinned, hint becomes composer placeholder, user types additional context before sending) - ChatPane: armed-prompt state management, argumentHint-driven placeholder, send button enabled when prompt is armed, clear armed state on session change - PromptInvocationChrome: model badge in transcript, parent-repo badge and shortened source path display for ancestor-relative paths - ProjectSettingsPanel: model and argumentHint on prompt cards, parent-repo badge on instructions and prompts with ancestor paths, .claude/rules mentioned in instructions description and empty state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { PlanReviewBanner } from '@renderer/components/chat/PlanReviewBanner';
|
||||
import { McpAuthBanner } from '@renderer/components/chat/McpAuthBanner';
|
||||
import { UserInputBanner } from '@renderer/components/chat/UserInputBanner';
|
||||
import { InlineApprovalPill, InlineGitPill, InlineModelPill, InlineTerminalPill, InlineThinkingPill, InlineToolsPill } from '@renderer/components/chat/InlinePills';
|
||||
import { InlinePromptPill } from '@renderer/components/chat/InlinePromptPill';
|
||||
import { InlinePromptPill, type ArmedPrompt } from '@renderer/components/chat/InlinePromptPill';
|
||||
import { ThinkingDots } from '@renderer/components/chat/ThinkingDots';
|
||||
import { ThinkingProcess } from '@renderer/components/chat/ThinkingProcess';
|
||||
import { SubagentActivityList } from '@renderer/components/chat/SubagentActivityCard';
|
||||
@@ -193,6 +193,7 @@ export function ChatPane({
|
||||
const canSubmitInput = hasComposerContent && !isComposerDisabled;
|
||||
const [pendingAttachments, setPendingAttachments] = useState<ChatMessageAttachment[]>([]);
|
||||
const promptFiles = useMemo(() => project.customization?.promptFiles ?? [], [project.customization?.promptFiles]);
|
||||
const [armedPrompt, setArmedPrompt] = useState<ArmedPrompt | null>(null);
|
||||
|
||||
const toolSelection = useMemo(() => resolveSessionToolingSelection(session), [session]);
|
||||
const mcpServers = toolingSettings.mcpServers;
|
||||
@@ -232,13 +233,24 @@ export function ChatPane({
|
||||
setIsResolvingApproval(false);
|
||||
setIsUpdatingSessionModelConfig(false);
|
||||
setEditingMessageId(undefined);
|
||||
setArmedPrompt(null);
|
||||
}, [session.id]);
|
||||
|
||||
function handleComposerSubmit(content: string) {
|
||||
const attachments = pendingAttachments.length > 0 ? [...pendingAttachments] : undefined;
|
||||
const messageMode: MessageMode | undefined = isSessionBusy ? 'immediate' : undefined;
|
||||
setPendingAttachments([]);
|
||||
void onSend(content, attachments, messageMode);
|
||||
|
||||
if (armedPrompt) {
|
||||
const invocation = { ...armedPrompt.invocation };
|
||||
if (content.trim()) {
|
||||
invocation.resolvedPrompt = `${invocation.resolvedPrompt}\n\n${content.trim()}`;
|
||||
}
|
||||
setArmedPrompt(null);
|
||||
void onSend('', attachments, messageMode, invocation);
|
||||
} else {
|
||||
void onSend(content, attachments, messageMode);
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyMessage = useCallback((content: string) => {
|
||||
@@ -747,21 +759,23 @@ export function ChatPane({
|
||||
onContentChange={setHasComposerContent}
|
||||
onSubmit={handleComposerSubmit}
|
||||
placeholder={
|
||||
pendingApproval
|
||||
? 'Awaiting approval...'
|
||||
: pendingUserInput
|
||||
? 'Awaiting your input above...'
|
||||
: pendingPlanReview
|
||||
? 'Review the plan above...'
|
||||
: pendingMcpAuth
|
||||
? 'MCP server requires authentication...'
|
||||
: isSessionBusy
|
||||
? 'Steer the agent (sends immediately)...'
|
||||
: isUpdatingSessionModelConfig
|
||||
? 'Saving model settings...'
|
||||
: isPlanMode
|
||||
? 'Describe what to plan...'
|
||||
: 'Message...'
|
||||
armedPrompt?.prompt.argumentHint
|
||||
? armedPrompt.prompt.argumentHint
|
||||
: pendingApproval
|
||||
? 'Awaiting approval...'
|
||||
: pendingUserInput
|
||||
? 'Awaiting your input above...'
|
||||
: pendingPlanReview
|
||||
? 'Review the plan above...'
|
||||
: pendingMcpAuth
|
||||
? 'MCP server requires authentication...'
|
||||
: isSessionBusy
|
||||
? 'Steer the agent (sends immediately)...'
|
||||
: isUpdatingSessionModelConfig
|
||||
? 'Saving model settings...'
|
||||
: isPlanMode
|
||||
? 'Describe what to plan...'
|
||||
: 'Message...'
|
||||
}
|
||||
>
|
||||
{/* Bottom action bar: left = shortcuts, right = buttons */}
|
||||
@@ -785,7 +799,10 @@ export function ChatPane({
|
||||
)}
|
||||
{!isScratchpad && promptFiles.length > 0 && (
|
||||
<InlinePromptPill
|
||||
armedPrompt={armedPrompt}
|
||||
disabled={isComposerDisabled}
|
||||
onArm={setArmedPrompt}
|
||||
onDisarm={() => setArmedPrompt(null)}
|
||||
onSubmit={(promptInvocation) => void onSend('', undefined, undefined, promptInvocation)}
|
||||
promptFiles={promptFiles}
|
||||
/>
|
||||
@@ -847,9 +864,9 @@ export function ChatPane({
|
||||
{/* Send / Stop / Steer button */}
|
||||
<button
|
||||
className={`flex size-8 items-center justify-center rounded-lg transition-all duration-150 ${
|
||||
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0
|
||||
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0 && !armedPrompt
|
||||
? 'bg-[var(--color-status-error)]/80 text-white hover:bg-[var(--color-status-error)]'
|
||||
: canSubmitInput || pendingAttachments.length > 0
|
||||
: canSubmitInput || pendingAttachments.length > 0 || armedPrompt
|
||||
? isSessionBusy
|
||||
? 'bg-[var(--color-status-warning)] text-white hover:brightness-110'
|
||||
: isPlanMode
|
||||
@@ -857,9 +874,9 @@ export function ChatPane({
|
||||
: 'brand-gradient-bg text-white shadow-[0_2px_12px_rgba(36,92,249,0.25)] hover:shadow-[0_4px_20px_rgba(36,92,249,0.35)]'
|
||||
: 'bg-[var(--color-surface-2)] text-[var(--color-text-muted)]'
|
||||
}`}
|
||||
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0}
|
||||
disabled={!canSubmitInput && !isSessionBusy && pendingAttachments.length === 0 && !armedPrompt}
|
||||
onClick={() => {
|
||||
if (isSessionBusy && !hasComposerContent && pendingAttachments.length === 0) {
|
||||
if (isSessionBusy && !hasComposerContent && pendingAttachments.length === 0 && !armedPrompt) {
|
||||
onCancelTurn?.();
|
||||
} else {
|
||||
composerRef.current?.submit();
|
||||
@@ -867,7 +884,7 @@ export function ChatPane({
|
||||
}}
|
||||
type="button"
|
||||
aria-label={
|
||||
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0
|
||||
isSessionBusy && !hasComposerContent && pendingAttachments.length === 0 && !armedPrompt
|
||||
? 'Stop generating'
|
||||
: isSessionBusy
|
||||
? 'Steer agent'
|
||||
@@ -933,8 +950,19 @@ export function ChatPane({
|
||||
|
||||
/* ── Prompt invocation chrome ───────────────────────────────── */
|
||||
|
||||
function formatInvocationSourcePath(sourcePath: string): string {
|
||||
const normalized = sourcePath.replace(/\\/g, '/');
|
||||
const ancestorPrefix = /^(\.\.\/)+(\.github|\.claude)\//;
|
||||
if (ancestorPrefix.test(normalized)) {
|
||||
const segments = normalized.split('/').filter((s) => s !== '..');
|
||||
return `↑ ${segments.join('/')}`;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function PromptInvocationChrome({ invocation }: { invocation: ProjectPromptInvocation }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const isAncestor = invocation.sourcePath.startsWith('..');
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-[var(--color-status-success)]/20 bg-[var(--color-status-success)]/5 px-4 py-3">
|
||||
@@ -949,6 +977,11 @@ function PromptInvocationChrome({ invocation }: { invocation: ProjectPromptInvoc
|
||||
{invocation.name}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{invocation.model && (
|
||||
<span className="rounded bg-[var(--color-accent-purple)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-purple)]">
|
||||
{invocation.model}
|
||||
</span>
|
||||
)}
|
||||
{invocation.agent && (
|
||||
<span className="rounded bg-[var(--color-accent-sky)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-sky)]">
|
||||
{invocation.agent}
|
||||
@@ -967,7 +1000,16 @@ function PromptInvocationChrome({ invocation }: { invocation: ProjectPromptInvoc
|
||||
{invocation.description && (
|
||||
<p className="mt-1 pl-6 text-[11px] text-[var(--color-text-muted)]">{invocation.description}</p>
|
||||
)}
|
||||
<p className="mt-0.5 pl-6 text-[10px] text-[var(--color-text-muted)]">{invocation.sourcePath}</p>
|
||||
<div className="mt-0.5 flex items-center gap-1.5 pl-6">
|
||||
{isAncestor && (
|
||||
<span className="rounded bg-[var(--color-surface-3)] px-1 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
|
||||
parent repo
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] text-[var(--color-text-muted)]" title={invocation.sourcePath}>
|
||||
{formatInvocationSourcePath(invocation.sourcePath)}
|
||||
</span>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="mt-2.5 rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-1)] p-3">
|
||||
<div className="max-h-48 overflow-y-auto text-[12px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||
|
||||
@@ -9,6 +9,13 @@ import type { ProjectAgentProfile, ProjectInstructionApplicationMode, ProjectIns
|
||||
|
||||
/* ── Types ────────────────────────────────────────────────── */
|
||||
|
||||
/** Shortens ancestor-relative source paths like `..\..\..\.github\prompts\x.md` to `↑ .github/prompts/x.md` */
|
||||
function formatSettingsSourcePath(sourcePath: string): string {
|
||||
const normalized = sourcePath.replace(/\\/g, '/');
|
||||
const segments = normalized.split('/').filter((s) => s !== '..');
|
||||
return `↑ ${segments.join('/')}`;
|
||||
}
|
||||
|
||||
type ProjectSettingsSection = 'overview' | 'instructions' | 'agents' | 'prompts' | 'mcp-servers' | 'danger-zone';
|
||||
|
||||
interface NavItem {
|
||||
@@ -335,7 +342,7 @@ function InstructionsContent({
|
||||
return (
|
||||
<div>
|
||||
<SectionHeader
|
||||
description="Repository instructions discovered from copilot-instructions.md, AGENTS.md, CLAUDE.md, and .github/instructions/**/*.instructions.md."
|
||||
description="Repository instructions discovered from copilot-instructions.md, AGENTS.md, CLAUDE.md, .github/instructions/**/*.instructions.md, and .claude/rules/**/*.md."
|
||||
title="Instructions"
|
||||
>
|
||||
<RescanButton onClick={onRescan} />
|
||||
@@ -343,7 +350,7 @@ function InstructionsContent({
|
||||
|
||||
{instructions.length === 0 ? (
|
||||
<EmptyState>
|
||||
No instruction files found. Add <code className="text-[var(--color-text-secondary)]">.github/copilot-instructions.md</code>, <code className="text-[var(--color-text-secondary)]">AGENTS.md</code>, <code className="text-[var(--color-text-secondary)]">CLAUDE.md</code>, or <code className="text-[var(--color-text-secondary)]">*.instructions.md</code> files under <code className="text-[var(--color-text-secondary)]">.github/instructions/</code>.
|
||||
No instruction files found. Add <code className="text-[var(--color-text-secondary)]">.github/copilot-instructions.md</code>, <code className="text-[var(--color-text-secondary)]">AGENTS.md</code>, <code className="text-[var(--color-text-secondary)]">CLAUDE.md</code>, <code className="text-[var(--color-text-secondary)]">*.instructions.md</code> files under <code className="text-[var(--color-text-secondary)]">.github/instructions/</code>, or <code className="text-[var(--color-text-secondary)]">*.md</code> files under <code className="text-[var(--color-text-secondary)]">.claude/rules/</code>.
|
||||
</EmptyState>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
@@ -359,7 +366,8 @@ function InstructionsContent({
|
||||
function InstructionCard({ instruction }: { instruction: ProjectInstructionFile }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const isLong = instruction.content.length > 300;
|
||||
const displayName = instruction.name ?? instruction.sourcePath;
|
||||
const isAncestor = instruction.sourcePath.startsWith('..');
|
||||
const displayName = instruction.name ?? (isAncestor ? formatSettingsSourcePath(instruction.sourcePath) : instruction.sourcePath);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)]">
|
||||
@@ -370,8 +378,13 @@ function InstructionCard({ instruction }: { instruction: ProjectInstructionFile
|
||||
>
|
||||
<FileCode2 className="size-4 shrink-0 text-[var(--color-text-accent)]" />
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]">{displayName}</span>
|
||||
<span className="truncate text-[13px] font-medium text-[var(--color-text-primary)]" title={instruction.sourcePath}>{displayName}</span>
|
||||
<InstructionModeBadge mode={instruction.applicationMode} />
|
||||
{isAncestor && (
|
||||
<span className="shrink-0 rounded bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
|
||||
parent repo
|
||||
</span>
|
||||
)}
|
||||
{instruction.applyTo && (
|
||||
<span
|
||||
className="truncate rounded bg-[var(--color-surface-3)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--color-text-muted)]"
|
||||
@@ -571,6 +584,8 @@ function PromptsContent({
|
||||
}
|
||||
|
||||
function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
||||
const isAncestor = prompt.sourcePath.startsWith('..');
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-glass)] px-5 py-4">
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -578,6 +593,11 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] font-medium text-[var(--color-text-primary)]">{prompt.name}</span>
|
||||
{prompt.model && (
|
||||
<span className="rounded-full bg-[var(--color-accent-purple)]/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-accent-purple)]">
|
||||
{prompt.model}
|
||||
</span>
|
||||
)}
|
||||
{prompt.agent && (
|
||||
<span className="rounded-full bg-[var(--color-accent-sky)]/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--color-accent-sky)]">
|
||||
{prompt.agent}
|
||||
@@ -588,10 +608,20 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
||||
{prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'}
|
||||
</span>
|
||||
)}
|
||||
{isAncestor && (
|
||||
<span className="rounded-full bg-[var(--color-surface-3)] px-2 py-0.5 text-[9px] font-medium text-[var(--color-text-muted)]">
|
||||
parent repo
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{prompt.description && (
|
||||
<p className="mt-1 text-[12px] leading-relaxed text-[var(--color-text-muted)]">{prompt.description}</p>
|
||||
)}
|
||||
{prompt.argumentHint && (
|
||||
<p className="mt-1 text-[11px] italic text-[var(--color-text-muted)]">
|
||||
Hint: {prompt.argumentHint}
|
||||
</p>
|
||||
)}
|
||||
{prompt.tools && prompt.tools.length > 0 && (
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{prompt.tools.map((tool) => (
|
||||
@@ -617,7 +647,9 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="mt-1.5 text-[11px] text-[var(--color-text-muted)]">{prompt.sourcePath}</p>
|
||||
<p className="mt-1.5 text-[11px] text-[var(--color-text-muted)]" title={prompt.sourcePath}>
|
||||
{isAncestor ? formatSettingsSourcePath(prompt.sourcePath) : prompt.sourcePath}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,18 +22,41 @@ function buildPromptInvocation(prompt: ProjectPromptFile, resolvedTemplate: stri
|
||||
|
||||
if (prompt.description) invocation.description = prompt.description;
|
||||
if (prompt.agent) invocation.agent = prompt.agent;
|
||||
if (prompt.model) invocation.model = prompt.model;
|
||||
if (prompt.tools?.length) invocation.tools = prompt.tools;
|
||||
|
||||
return invocation;
|
||||
}
|
||||
|
||||
/** Returns a human-friendly display label for source paths, shortening ancestor-relative segments. */
|
||||
function formatPromptSourcePath(sourcePath: string): string {
|
||||
const normalized = sourcePath.replace(/\\/g, '/');
|
||||
const ancestorPrefix = /^(\.\.\/)+(\.github|\.claude)\//;
|
||||
if (ancestorPrefix.test(normalized)) {
|
||||
const segments = normalized.split('/').filter((s) => s !== '..');
|
||||
return `↑ ${segments.join('/')}`;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export interface ArmedPrompt {
|
||||
prompt: ProjectPromptFile;
|
||||
invocation: ProjectPromptInvocation;
|
||||
}
|
||||
|
||||
export function InlinePromptPill({
|
||||
promptFiles,
|
||||
disabled,
|
||||
armedPrompt,
|
||||
onArm,
|
||||
onDisarm,
|
||||
onSubmit,
|
||||
}: {
|
||||
promptFiles: ReadonlyArray<ProjectPromptFile>;
|
||||
disabled: boolean;
|
||||
armedPrompt?: ArmedPrompt | null;
|
||||
onArm?: (armed: ArmedPrompt) => void;
|
||||
onDisarm?: () => void;
|
||||
onSubmit: (invocation: ProjectPromptInvocation) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -47,23 +70,32 @@ export function InlinePromptPill({
|
||||
setVariableValues({});
|
||||
}, []);
|
||||
|
||||
const armOrSubmit = useCallback((prompt: ProjectPromptFile, resolvedTemplate: string) => {
|
||||
const invocation = buildPromptInvocation(prompt, resolvedTemplate);
|
||||
if (prompt.argumentHint && onArm) {
|
||||
onArm({ prompt, invocation });
|
||||
handleClose();
|
||||
} else {
|
||||
onSubmit(invocation);
|
||||
handleClose();
|
||||
}
|
||||
}, [onArm, onSubmit, handleClose]);
|
||||
|
||||
const handleSelectPrompt = useCallback((prompt: ProjectPromptFile) => {
|
||||
if (prompt.variables.length === 0) {
|
||||
onSubmit(buildPromptInvocation(prompt, prompt.template.trim()));
|
||||
handleClose();
|
||||
armOrSubmit(prompt, prompt.template.trim());
|
||||
} else {
|
||||
setSelectedPrompt(prompt);
|
||||
setVariableValues({});
|
||||
}
|
||||
}, [onSubmit, handleClose]);
|
||||
}, [armOrSubmit]);
|
||||
|
||||
const handleSubmitWithVariables = useCallback(() => {
|
||||
if (!selectedPrompt) return;
|
||||
const resolved = resolvePromptTemplate(selectedPrompt.template, variableValues).trim();
|
||||
if (!resolved) return;
|
||||
onSubmit(buildPromptInvocation(selectedPrompt, resolved));
|
||||
handleClose();
|
||||
}, [selectedPrompt, variableValues, onSubmit, handleClose]);
|
||||
armOrSubmit(selectedPrompt, resolved);
|
||||
}, [selectedPrompt, variableValues, armOrSubmit]);
|
||||
|
||||
const handleVariableChange = useCallback((name: string, value: string) => {
|
||||
setVariableValues((prev) => ({ ...prev, [name]: value }));
|
||||
@@ -78,18 +110,31 @@ export function InlinePromptPill({
|
||||
|
||||
return (
|
||||
<div className="relative" ref={ref}>
|
||||
<button
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||
disabled={disabled}
|
||||
onClick={() => setOpen(!open)}
|
||||
type="button"
|
||||
>
|
||||
<FileText className="size-3" />
|
||||
Prompts
|
||||
<span className="text-[var(--color-text-muted)]">({promptFiles.length})</span>
|
||||
</button>
|
||||
{armedPrompt ? (
|
||||
<button
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-status-success)]/10 px-2.5 py-1 text-[11px] font-medium text-[var(--color-status-success)] transition-all duration-200 hover:bg-[var(--color-status-success)]/20"
|
||||
onClick={() => onDisarm?.()}
|
||||
type="button"
|
||||
aria-label={`Disarm prompt: ${armedPrompt.prompt.name}`}
|
||||
>
|
||||
<FileText className="size-3" />
|
||||
<span className="max-w-[120px] truncate">{armedPrompt.prompt.name}</span>
|
||||
<X className="size-3 opacity-60" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
className="inline-flex items-center gap-1 rounded-lg px-2 py-1 text-[11px] font-medium text-[var(--color-text-muted)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
|
||||
disabled={disabled}
|
||||
onClick={() => setOpen(!open)}
|
||||
type="button"
|
||||
>
|
||||
<FileText className="size-3" />
|
||||
Prompts
|
||||
<span className="text-[var(--color-text-muted)]">({promptFiles.length})</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{open && !disabled && (
|
||||
<div
|
||||
@@ -151,6 +196,11 @@ function PromptList({
|
||||
{prompt.agent}
|
||||
</span>
|
||||
)}
|
||||
{prompt.model && (
|
||||
<span className="shrink-0 rounded bg-[var(--color-accent-purple)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-purple)]">
|
||||
{prompt.model}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{prompt.description && (
|
||||
<div className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
|
||||
@@ -163,6 +213,11 @@ function PromptList({
|
||||
{prompt.tools.length} tool{prompt.tools.length === 1 ? '' : 's'}
|
||||
</span>
|
||||
)}
|
||||
{prompt.argumentHint && (
|
||||
<span className="rounded bg-[var(--color-surface-2)] px-1.5 py-0.5 text-[10px] italic text-[var(--color-text-muted)]">
|
||||
hint: {prompt.argumentHint}
|
||||
</span>
|
||||
)}
|
||||
{prompt.variables.map((v) => (
|
||||
<span
|
||||
key={v.name}
|
||||
@@ -172,6 +227,11 @@ function PromptList({
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{prompt.sourcePath.startsWith('..') && (
|
||||
<div className="mt-0.5 text-[10px] text-[var(--color-text-muted)]">
|
||||
{formatPromptSourcePath(prompt.sourcePath)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -206,8 +266,15 @@ function PromptVariableForm({
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
|
||||
{prompt.name}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="truncate text-[12px] font-medium text-[var(--color-text-primary)]">
|
||||
{prompt.name}
|
||||
</span>
|
||||
{prompt.model && (
|
||||
<span className="shrink-0 rounded bg-[var(--color-accent-purple)]/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-[var(--color-accent-purple)]">
|
||||
{prompt.model}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{prompt.description && (
|
||||
<div className="truncate text-[10px] text-[var(--color-text-muted)]">{prompt.description}</div>
|
||||
@@ -238,7 +305,7 @@ function PromptVariableForm({
|
||||
type="button"
|
||||
>
|
||||
<ArrowUp className="size-3.5" />
|
||||
Send prompt
|
||||
{prompt.argumentHint ? 'Arm prompt' : 'Send prompt'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user