refactor: resolve merge conflicts — combine main improvements with token normalization

- .gitignore: keep both graphify-out/ entries and main's .gitnexus entry
- ScriptCodeBlock/ScriptPreviewModal: take main's border-border and text-accent-text
  for filename labels; use neutral ghost style for Save button in ScriptCodeBlock;
  use bg-accent (normalized from bg-primary) for Save button in ScriptPreviewModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-04-06 20:23:36 -04:00
51 changed files with 4039 additions and 2656 deletions

View File

@@ -189,7 +189,7 @@ function ChatItem({
return (
<div
onClick={onSelect}
onClick={confirming ? e => e.stopPropagation() : onSelect}
className={cn(
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
confirming

View File

@@ -130,6 +130,14 @@ export function TaskLane({ questions, actions, sessionId, onSubmit, onClose, loa
}
}, [handleMouseMove, handleMouseUp])
// Refs so the debounced save always uses the latest questions/actions/tasks
const questionsRef = useRef(questions)
const actionsRef = useRef(actions)
const tasksRef = useRef(tasks)
useEffect(() => { questionsRef.current = questions }, [questions])
useEffect(() => { actionsRef.current = actions }, [actions])
useEffect(() => { tasksRef.current = tasks }, [tasks])
// Save task state to sessionStorage on every change + debounce to backend
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
@@ -139,9 +147,9 @@ export function TaskLane({ questions, actions, sessionId, onSubmit, onClose, loa
if (saveTimerRef.current) clearTimeout(saveTimerRef.current)
saveTimerRef.current = setTimeout(() => {
aiSessionsApi.saveTaskLane(sessionId, {
questions: questions.map(q => ({ text: q.text, context: q.context })),
actions: actions.map(a => ({ label: a.label, command: a.command, description: a.description })),
responses: tasks as unknown as Array<Record<string, unknown>>,
questions: questionsRef.current.map(q => ({ text: q.text, context: q.context })),
actions: actionsRef.current.map(a => ({ label: a.label, command: a.command, description: a.description })),
responses: tasksRef.current as unknown as Array<Record<string, unknown>>,
}).catch(() => { /* silent — best-effort save */ })
}, 2000)
return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current) }

View File

@@ -1,65 +1,19 @@
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Plus, ChevronDown, Sparkles, FolderTree, ListOrdered } from 'lucide-react'
import { Link } from 'react-router-dom'
import { Plus, ChevronDown, FolderTree, ListOrdered } from 'lucide-react'
import { cn } from '@/lib/utils'
import { editorAIApi } from '@/api/editorAI'
import { apiClient } from '@/api/client'
import { AIPromptDialog } from '@/components/editor-ai/AIPromptDialog'
type AIFlowType = 'troubleshooting' | 'procedural' | 'maintenance'
interface CreateFlowDropdownProps {
aiEnabled: boolean
className?: string
/** Button label — defaults to "Create Flow" */
label?: string
}
export function CreateFlowDropdown({
aiEnabled,
className,
label = 'Create Flow',
}: CreateFlowDropdownProps) {
const [showMenu, setShowMenu] = useState(false)
const [aiPromptOpen, setAiPromptOpen] = useState(false)
const [aiPromptFlowType, setAiPromptFlowType] = useState<AIFlowType>('troubleshooting')
const navigate = useNavigate()
const handleAIGenerate = async (prompt: string) => {
// Start an AI session
const session = await editorAIApi.startSession(
aiPromptFlowType === 'maintenance' ? 'procedural' : aiPromptFlowType
)
const sessionId = session.session_id
// Send the user's prompt
await editorAIApi.sendMessage({
sessionId,
content: prompt,
actionType: 'generate_full',
})
// Generate the full flow
await editorAIApi.generateFull(sessionId)
// Import to create the tree
const { data: importResult } = await apiClient.post(
`/ai/chat/sessions/${sessionId}/import`,
{}
)
const treeId = importResult.tree_id
// Navigate to the editor
if (aiPromptFlowType === 'troubleshooting') {
navigate(`/trees/${treeId}/edit`, {
state: { aiPanelOpen: true, sessionId },
})
} else {
navigate(`/flows/${treeId}/edit`, {
state: { aiPanelOpen: true, sessionId },
})
}
}
return (
<div className={cn('relative', className)}>
@@ -74,43 +28,25 @@ export function CreateFlowDropdown({
{showMenu && (
<>
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl">
{/* Troubleshooting */}
<div className="absolute right-0 z-20 mt-1 w-60 rounded-lg border border-border bg-card p-1 shadow-xl">
<Link
to="/trees/new"
onClick={() => setShowMenu(false)}
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-[var(--color-bg-elevated)] transition-colors"
>
<FolderTree className="h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<div className="font-medium">Troubleshooting Tree</div>
<div className="font-medium">Troubleshooting Flow</div>
<div className="text-xs text-muted-foreground">Branching decision flow</div>
</div>
</Link>
{aiEnabled && (
<button
type="button"
onClick={() => {
setShowMenu(false)
setAiPromptFlowType('troubleshooting')
setAiPromptOpen(true)
}}
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
>
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
<div className="text-left">
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
</div>
</button>
)}
<div className="my-1 border-t border-border" />
{/* Procedural */}
<Link
to="/flows/new"
onClick={() => setShowMenu(false)}
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-[var(--color-bg-elevated)] transition-colors"
>
<ListOrdered className="h-4 w-4 text-muted-foreground" />
<div className="flex-1">
@@ -118,51 +54,9 @@ export function CreateFlowDropdown({
<div className="text-xs text-muted-foreground">Step-by-step procedure</div>
</div>
</Link>
{aiEnabled && (
<button
type="button"
onClick={() => {
setShowMenu(false)
setAiPromptFlowType('procedural')
setAiPromptOpen(true)
}}
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
>
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
<div className="text-left">
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
</div>
</button>
)}
<div className="my-1 border-t border-border" />
{aiEnabled && (
<button
type="button"
onClick={() => {
setShowMenu(false)
setAiPromptFlowType('procedural')
setAiPromptOpen(true)
}}
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
>
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
<div className="text-left">
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
</div>
</button>
)}
</div>
</>
)}
<AIPromptDialog
isOpen={aiPromptOpen}
onClose={() => setAiPromptOpen(false)}
onGenerate={handleAIGenerate}
flowType={aiPromptFlowType}
/>
</div>
)
}

View File

@@ -34,11 +34,11 @@ export function TagBadges({
}}
disabled={!onTagClick}
className={cn(
'rounded-full font-sans text-xs transition-colors',
'rounded-full font-sans transition-colors',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
variant === 'default'
? 'bg-accent text-muted-foreground hover:bg-accent'
: 'bg-accent/50 text-muted-foreground hover:bg-accent',
? 'bg-[var(--color-bg-elevated)] text-muted-foreground border border-border hover:text-foreground hover:border-[var(--color-border-hover)]'
: 'bg-[rgba(255,255,255,0.04)] text-muted-foreground border border-border hover:text-foreground',
!onTagClick && 'cursor-default'
)}
>
@@ -48,9 +48,9 @@ export function TagBadges({
{hiddenCount > 0 && (
<span
className={cn(
'rounded-full font-sans text-xs',
'rounded-full font-sans',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
'bg-accent/50 text-muted-foreground'
'bg-[rgba(255,255,255,0.04)] text-muted-foreground border border-border'
)}
title={tags.slice(maxVisible).join(', ')}
>

View File

@@ -3,12 +3,21 @@ import { useNavigate } from 'react-router-dom'
import { AlertTriangle, Clock, Hash, Ticket, Loader2, RefreshCw } from 'lucide-react'
import { aiSessionsApi } from '@/api'
import type { AISessionSummary } from '@/types/ai-session'
import { timeAgo } from '@/lib/timeAgo'
interface EscalationQueueProps {
onPickup?: (sessionId: string) => void
onCountChange?: (count: number) => void
}
export function EscalationQueue({ onPickup }: EscalationQueueProps) {
function waitTimeColor(createdAt: string): string {
const hours = (Date.now() - new Date(createdAt).getTime()) / 3_600_000
if (hours >= 4) return '#f87171' // danger
if (hours >= 1) return '#fbbf24' // warning/amber
return '#848b9b' // muted
}
export function EscalationQueue({ onPickup, onCountChange }: EscalationQueueProps) {
const navigate = useNavigate()
const [sessions, setSessions] = useState<AISessionSummary[]>([])
const [isLoading, setIsLoading] = useState(true)
@@ -19,7 +28,12 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
setError(null)
try {
const data = await aiSessionsApi.getEscalationQueue()
setSessions(data)
// Sort oldest-first — longest waiting = most urgent
const sorted = [...data].sort(
(a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
)
setSessions(sorted)
onCountChange?.(sorted.length)
} catch {
setError('Failed to load escalation queue')
} finally {
@@ -29,6 +43,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
useEffect(() => {
loadQueue()
// eslint-disable-next-line react-hooks/exhaustive-deps -- load once on mount
}, [])
const handlePickup = (sessionId: string) => {
@@ -80,7 +95,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
return (
<div className="space-y-3">
<div className="flex items-center justify-between px-1">
<h3 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted">
<h3 className="font-sans text-[0.625rem] uppercase tracking-wider text-muted-foreground">
Awaiting pickup ({sessions.length})
</h3>
<button
@@ -93,7 +108,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
</div>
{sessions.map((session) => (
<div key={session.id} className="card-interactive p-3 sm:p-4 space-y-3">
<div key={session.id} className="card-flat p-3 sm:p-4 space-y-3">
<div>
<p className="text-sm font-semibold text-foreground">
{session.problem_summary || 'Untitled session'}
@@ -107,7 +122,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{session.problem_domain && (
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
<span className="font-sans rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-accent-text">
{session.problem_domain}
</span>
)}
@@ -115,24 +130,29 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
<Hash size={10} />
{session.step_count} steps
</span>
<span className="flex items-center gap-1">
<span
className="flex items-center gap-1 font-medium"
style={{ color: waitTimeColor(session.created_at) }}
>
<Clock size={10} />
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{timeAgo(session.created_at)}
</span>
{session.psa_ticket_id && (
<span className="flex items-center gap-1 text-primary">
<span className="flex items-center gap-1 text-accent-text">
<Ticket size={10} />
#{session.psa_ticket_id}
</span>
)}
</div>
<button
onClick={() => handlePickup(session.id)}
className="w-full min-h-[44px] rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
>
Pick Up Session
</button>
<div className="flex justify-end">
<button
onClick={() => handlePickup(session.id)}
className="rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
>
Pick Up
</button>
</div>
</div>
))}
</div>

View File

@@ -187,6 +187,23 @@ export function SessionDocView({
))}
</div>
{/* Follow-up recommendations */}
{documentation.follow_up_recommendations.length > 0 && (
<div className="card-flat p-3 sm:p-4">
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
Follow-up
</h4>
<ul className="space-y-1">
{documentation.follow_up_recommendations.map((rec, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-foreground">
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary" />
{rec}
</li>
))}
</ul>
</div>
)}
{/* Rating */}
{onRate && (
<div className="card-flat p-3 sm:p-4 text-center">

View File

@@ -1,5 +1,5 @@
import { useState } from 'react'
import { FileText, User, Mail, Zap, AlignLeft, Copy, Check, RotateCcw, ArrowLeftRight, Loader2 } from 'lucide-react'
import { FileText, User, Mail, HelpCircle, Zap, AlignLeft, Copy, Check, RotateCcw, ArrowLeftRight, Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import type { StatusUpdateAudience, StatusUpdateLength, StatusUpdateContext, StatusUpdateResponse } from '@/types/ai-session'
@@ -12,10 +12,11 @@ interface StatusUpdateModalProps {
hasPsaTicket?: boolean
}
const AUDIENCES: { value: StatusUpdateAudience; icon: typeof FileText; label: string; description: string }[] = [
const AUDIENCES: { value: StatusUpdateAudience; icon: typeof FileText; label: string; description: string; skipLength?: boolean }[] = [
{ value: 'ticket_notes', icon: FileText, label: 'Ticket Notes', description: 'Technical, for your PSA' },
{ value: 'client_update', icon: User, label: 'Client Update', description: 'Professional, non-technical' },
{ value: 'email_draft', icon: Mail, label: 'Email Draft', description: 'Full email with subject line' },
{ value: 'request_info', icon: HelpCircle, label: 'Request Information', description: 'Ask the client specific questions', skipLength: true },
]
const LENGTHS: { value: StatusUpdateLength; icon: typeof Zap; label: string; description: string }[] = [
@@ -38,9 +39,24 @@ export function StatusUpdateModal({ open, onClose, onGenerate, context = 'status
escalation: 'Share Escalation',
}
const handleAudienceSelect = (value: StatusUpdateAudience) => {
const handleAudienceSelect = async (value: StatusUpdateAudience) => {
setAudience(value)
setStep('length')
const opt = AUDIENCES.find(a => a.value === value)
if (opt?.skipLength) {
// Skip length selection — always concise for request_info
setLength('quick')
setStep('generating')
try {
const res = await onGenerate(value, 'quick', context)
setResult(res)
setStep('result')
} catch {
setStep('audience')
setAudience(null)
}
} else {
setStep('length')
}
}
const handleLengthSelect = async (value: StatusUpdateLength) => {
@@ -170,7 +186,7 @@ export function StatusUpdateModal({ open, onClose, onGenerate, context = 'status
<div className="flex flex-col items-center justify-center py-8 gap-3">
<Loader2 size={24} className="animate-spin text-accent" />
<p className="text-sm text-muted-foreground">
Generating {audience === 'email_draft' ? 'email draft' : audience === 'client_update' ? 'client update' : 'ticket notes'}...
{audience === 'request_info' ? 'Drafting information request...' : audience === 'email_draft' ? 'Generating email draft...' : audience === 'client_update' ? 'Generating client update...' : 'Generating ticket notes...'}
</p>
</div>
)}

View File

@@ -47,11 +47,15 @@ export function Sidebar() {
const [flyoutIndex, setFlyoutIndex] = useState<string | null>(null)
const flyoutTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const sidebarRef = useRef<HTMLElement>(null)
const statsRequestId = useRef(0)
/* ── Stats fetching ───────────────────────────────── */
const refreshStats = useCallback(() => {
sidebarApi.getStats().then(setStats).catch(() => {})
const requestId = ++statsRequestId.current
sidebarApi.getStats()
.then(data => { if (requestId === statsRequestId.current) setStats(data) })
.catch(() => {})
}, [])
useEffect(() => { refreshStats() }, [location.pathname, refreshStats])
@@ -84,8 +88,7 @@ export function Sidebar() {
badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/review-queue'],
children: [
{ href: '/trees', label: 'Guided Flows', count: stats?.tree_counts.total || undefined },
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
{ href: '/trees', label: 'Flow Library', count: stats?.tree_counts.total || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/step-library', label: 'Solutions Library' },
{ href: '/review-queue', label: 'Review Queue' },
@@ -123,12 +126,11 @@ export function Sidebar() {
title: 'KNOWLEDGE',
items: [
{
href: '/trees', icon: GitBranch, label: 'Guided Flows', shortLabel: 'Flows',
href: '/trees', icon: GitBranch, label: 'Flow Library', shortLabel: 'Flows',
badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees'],
children: [
{ href: '/trees', label: 'All Flows' },
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
{ href: '/trees', label: 'Flow Library' },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
],
},

View File

@@ -41,7 +41,7 @@ export function CollapsibleEditorSection({
onClick={handleToggle}
aria-expanded={isExpanded}
aria-controls={sectionId}
className="flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-accent/50"
className="flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-white/[0.05]"
>
<ChevronRight
className={cn(

View File

@@ -30,7 +30,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
<span className="text-sm font-medium text-muted-foreground">Edit Section Header</span>
<button
onClick={onCollapse}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded p-1 text-muted-foreground hover:bg-elevated hover:text-foreground"
>
<ChevronUp className="h-4 w-4" />
</button>
@@ -42,7 +42,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
placeholder="Section title"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
</div>
@@ -54,14 +54,14 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-accent text-xs font-medium text-foreground">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-white/[0.10] text-xs font-medium text-foreground">
{stepNumber}
</span>
<span className="text-sm font-medium text-foreground">Edit Step</span>
</div>
<button
onClick={onCollapse}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded p-1 text-muted-foreground hover:bg-elevated hover:text-foreground"
>
<ChevronUp className="h-4 w-4" />
</button>
@@ -75,7 +75,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
type="text"
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -91,7 +91,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
placeholder="—"
min={1}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -103,7 +103,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ description: e.target.value })}
placeholder="Step instructions. Use [VAR:name] for variables."
rows={4}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
{availableVariables.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
@@ -132,44 +132,44 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
rows={3}
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
{/* More Options toggle */}
{/* Content Type */}
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Type</label>
<div className="flex gap-1">
{CONTENT_TYPE_OPTIONS.map((opt) => (
<button
key={opt.value}
onClick={() => onUpdate({ content_type: opt.value })}
className={cn(
'rounded px-2 py-1 text-xs font-medium transition-colors',
step.content_type === opt.value
? 'bg-white/15 ' + opt.color
: 'text-muted-foreground hover:bg-elevated hover:text-foreground'
)}
>
{opt.label}
</button>
))}
</div>
</div>
{/* Advanced Options toggle */}
<button
type="button"
onClick={() => setShowMore(!showMore)}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-muted-foreground"
className="flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
>
<Settings2 className="h-3 w-3" />
More Options
Advanced Options
{showMore ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
</button>
{showMore && (
<div className="space-y-4 border-t border-border pt-4">
{/* Content Type */}
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Content Type</label>
<div className="flex gap-1">
{CONTENT_TYPE_OPTIONS.map((opt) => (
<button
key={opt.value}
onClick={() => onUpdate({ content_type: opt.value })}
className={cn(
'rounded px-2 py-1 text-xs font-medium transition-colors',
step.content_type === opt.value
? 'bg-white/15 ' + opt.color
: 'text-muted-foreground hover:bg-accent hover:text-muted-foreground'
)}
>
{opt.label}
</button>
))}
</div>
</div>
{/* Warning text */}
{(step.content_type === 'warning' || step.warning_text) && (
<div>
@@ -195,7 +195,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.expected_outcome || ''}
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
placeholder="Server should respond with..."
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -211,7 +211,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.verification_prompt || ''}
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
placeholder="Confirm the role was installed"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
@@ -219,7 +219,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
<select
value={step.verification_type || ''}
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
<option value="">None</option>
<option value="checkbox">Checkbox (confirm done)</option>
@@ -240,7 +240,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.reference_url || ''}
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
placeholder="https://learn.microsoft.com/..."
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-elevated px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div className="flex items-end pb-1">
@@ -267,7 +267,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
})}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-elevated px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
<option value="">Inherit from flow</option>
<option value="team">Team only</option>

View File

@@ -1,5 +1,5 @@
import { useRef, useEffect, useCallback, type ReactNode } from 'react'
import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, Shield, SeparatorHorizontal } from 'lucide-react'
import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, ListOrdered, SeparatorHorizontal } from 'lucide-react'
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
import type { DragEndEvent } from '@dnd-kit/core'
import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable'
@@ -104,7 +104,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
<div>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Shield className="h-5 w-5 text-muted-foreground" />
<ListOrdered className="h-5 w-5 text-muted-foreground" />
<h2 className="text-lg font-semibold text-foreground">Steps</h2>
<span className="text-sm text-muted-foreground">
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''}
@@ -114,14 +114,14 @@ export function StepList({ onStepContextMenu }: StepListProps) {
<div className="flex items-center gap-2">
<button
onClick={() => addSectionHeader()}
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-elevated hover:text-foreground"
>
<SeparatorHorizontal className="h-3.5 w-3.5" />
Add Section
</button>
<button
onClick={() => addStep()}
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-elevated hover:text-foreground"
>
<Plus className="h-3.5 w-3.5" />
Add Step
@@ -138,7 +138,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
return (
<div
key={step.id}
className="flex items-center gap-2 rounded-lg border border-dashed border-border bg-accent/50 px-3 py-2"
className="flex items-center gap-2 rounded-lg border border-dashed border-border bg-elevated/40 px-3 py-2"
>
<CheckCircle2 className="h-4 w-4 text-emerald-400/50" />
<input
@@ -238,7 +238,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
<div
className={cn(
'group flex flex-col rounded-xl border border-border px-3 py-2.5 transition-colors',
'hover:border-primary/30 hover:bg-accent/50',
'hover:border-white/[0.15] hover:bg-elevated',
isGhost && 'border-l-2 border-dashed border-l-primary/40! opacity-60'
)}
data-step-id={step.id}
@@ -254,7 +254,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
<GripVertical className="h-4 w-4" />
</button>
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent text-xs font-medium text-muted-foreground">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/[0.10] text-xs font-medium text-muted-foreground">
{stepNumber}
</span>
@@ -277,7 +277,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
<button
onClick={() => setExpandedStepId(step.id)}
className="shrink-0 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
className="shrink-0 rounded p-1 text-muted-foreground hover:bg-elevated hover:text-foreground"
>
<ChevronDown className="h-3.5 w-3.5" />
</button>
@@ -324,7 +324,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
{/* Add step button at bottom */}
<button
onClick={() => addStep()}
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border py-2 text-sm text-muted-foreground transition-colors hover:border-primary/30 hover:text-muted-foreground"
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/20 py-2 text-sm text-muted-foreground transition-colors hover:border-primary/40 hover:bg-elevated/30 hover:text-foreground"
>
<Plus className="h-3.5 w-3.5" />
Add Step

View File

@@ -52,7 +52,10 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
) : (
<Circle className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent text-[10px] font-medium">
<span className={cn(
'flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[10px] font-bold',
isCurrent ? 'bg-[#0e1016] text-accent' : 'bg-accent text-[#0e1016]'
)}>
{index + 1}
</span>
<span className="min-w-0 flex-1 flex items-center gap-1.5 overflow-hidden">

View File

@@ -91,7 +91,7 @@ export function StepDetail({
<div className="space-y-4">
{/* Step header */}
<div className="flex items-start gap-3">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent text-sm font-semibold text-foreground">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent text-sm font-bold text-[#0e1016]">
{stepNumber}
</span>
<div className="min-w-0 flex-1">

View File

@@ -1,17 +1,27 @@
import { useState, useRef, useCallback, useEffect } from 'react'
import { Send } from 'lucide-react'
import { Send, Terminal, UserPlus, HardDrive, RotateCcw } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
const SUGGESTIONS: { icon: LucideIcon; label: string }[] = [
{ icon: UserPlus, label: 'Create a new AD user' },
{ icon: HardDrive, label: 'Check disk space on all servers' },
{ icon: RotateCcw, label: 'Restart a Windows service' },
{ icon: Terminal, label: 'Reset MFA for a user' },
]
interface ScriptBuilderInputProps {
onSend: (content: string) => void
disabled: boolean
placeholder?: string
showSuggestions?: boolean
}
export function ScriptBuilderInput({
onSend,
disabled,
placeholder = 'Describe the script you need...',
showSuggestions = false,
}: ScriptBuilderInputProps) {
const [value, setValue] = useState('')
const textareaRef = useRef<HTMLTextAreaElement>(null)
@@ -44,35 +54,54 @@ export function ScriptBuilderInput({
const canSend = value.trim().length > 0 && !disabled
return (
<div className="flex items-end gap-2 p-3 border-t" style={{ borderColor: 'var(--color-border-default)' }}>
<textarea
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={disabled}
rows={1}
className={cn(
"flex-1 resize-none rounded-xl px-4 py-2.5 text-sm",
"bg-card border border-border text-foreground placeholder:text-muted-foreground",
"focus:outline-none focus:border-[rgba(96,165,250,0.3)] transition-colors",
"disabled:opacity-50"
)}
style={{ maxHeight: 120 }}
/>
<button
onClick={handleSend}
disabled={!canSend}
className={cn(
"shrink-0 flex items-center justify-center w-10 h-10 rounded-xl transition-all",
canSend
? "bg-primary text-white hover:brightness-110 active:scale-[0.98]"
: "bg-[rgba(255,255,255,0.04)] text-text-muted cursor-not-allowed"
)}
>
<Send size={18} />
</button>
<div className="border-t border-border p-3 space-y-2">
<div className="flex items-end gap-2">
<textarea
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={disabled}
rows={1}
className={cn(
"flex-1 resize-none rounded-xl px-4 py-2.5 text-sm",
"bg-card border border-border text-foreground placeholder:text-muted-foreground",
"focus:outline-none focus:border-[rgba(249,115,22,0.25)] focus:ring-1 focus:ring-[rgba(249,115,22,0.1)] transition-colors",
"disabled:opacity-50"
)}
style={{ maxHeight: 120 }}
/>
<button
onClick={handleSend}
disabled={!canSend}
className={cn(
"shrink-0 flex items-center justify-center w-10 h-10 rounded-xl transition-all",
canSend
? "bg-primary text-white hover:brightness-110 active:scale-[0.98]"
: "bg-[var(--color-bg-elevated)] text-muted-foreground cursor-not-allowed"
)}
>
<Send size={18} />
</button>
</div>
{showSuggestions && (
<div className="flex flex-wrap gap-2">
{SUGGESTIONS.map(({ icon: Icon, label }) => (
<button
key={label}
type="button"
disabled={disabled}
onClick={() => { if (!disabled) onSend(label) }}
className="group flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground transition-all hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-elevated)] hover:text-foreground active:scale-[0.97] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"
>
<Icon size={11} className="text-muted shrink-0 group-hover:text-[#f97316] transition-colors" />
{label}
</button>
))}
</div>
)}
</div>
)
}

View File

@@ -5,7 +5,6 @@ import bash from 'react-syntax-highlighter/dist/esm/languages/hljs/bash'
import python from 'react-syntax-highlighter/dist/esm/languages/hljs/python'
import atomOneDark from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark'
import { Eye, Copy, Check, BookmarkPlus } from 'lucide-react'
import { cn } from '@/lib/utils'
SyntaxHighlighter.registerLanguage('powershell', powershell)
SyntaxHighlighter.registerLanguage('bash', bash)
@@ -52,10 +51,10 @@ export function ScriptCodeBlock({
}
return (
<div className="mt-3 rounded-lg border bg-[rgba(0,0,0,0.3)] border-[rgba(255,255,255,0.06)] overflow-hidden">
<div className="mt-3 rounded-lg border border-border bg-[var(--color-bg-code)] overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between px-3 py-2 border-b border-[rgba(255,255,255,0.06)]">
<span className="font-mono text-xs text-accent truncate">
<div className="flex items-center justify-between px-3 py-2 border-b border-border">
<span className="font-mono text-xs text-accent-text truncate">
{filename || 'script'}
</span>
{lineCount != null && (
@@ -85,40 +84,31 @@ export function ScriptCodeBlock({
{previewLines}
</SyntaxHighlighter>
{remainingLines > 0 && (
<div className="px-3 pb-2 font-mono text-[0.625rem] text-text-muted">
<div className="px-3 pb-2 font-mono text-[0.625rem] text-muted-foreground">
{"··· "}{remainingLines} more line{remainingLines !== 1 ? 's' : ''}
</div>
)}
</button>
{/* Action buttons */}
<div className="flex items-center gap-2 px-3 py-2 border-t border-[rgba(255,255,255,0.06)]">
<div className="flex items-center gap-2 px-3 py-2 border-t border-border">
<button
onClick={onViewFull}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all",
"bg-primary text-white hover:brightness-110 active:scale-[0.98]"
)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-all bg-primary text-white hover:brightness-110 active:scale-[0.98]"
>
<Eye size={14} />
View Full Script
</button>
<button
onClick={handleCopy}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
"bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)]"
)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors border border-border text-foreground hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-elevated)]"
>
{copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
{copied ? 'Copied' : 'Copy'}
</button>
<button
onClick={(e) => { e.stopPropagation(); onSave() }}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
"bg-success-dim border border-success/20 text-success hover:bg-emerald-500/15"
)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors border border-default text-secondary hover:text-primary hover:border-hover hover:bg-elevated"
>
<BookmarkPlus size={14} />
Save to Library

View File

@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
import atomOneDark from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark'
import { X, Copy, Check, BookmarkPlus } from 'lucide-react'
import { cn } from '@/lib/utils'
const LANGUAGE_MAP: Record<string, string> = {
powershell: 'powershell',
@@ -55,44 +54,38 @@ export function ScriptPreviewModal({
return (
<div
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center"
className="fixed inset-0 z-50 bg-black/40 flex items-center justify-center"
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
>
<div className="bg-card rounded-xl border border-[rgba(255,255,255,0.08)] max-w-[900px] w-full mx-4 max-h-[85vh] flex flex-col">
<div className="bg-card rounded-xl border border-border max-w-[900px] w-full mx-4 max-h-[85vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between px-5 py-3.5 border-b border-[rgba(255,255,255,0.06)]">
<div className="flex items-center justify-between px-5 py-3.5 border-b border-border">
<div className="flex items-center gap-3 min-w-0">
<span className="font-mono text-sm text-accent truncate">
<span className="font-mono text-sm text-accent-text truncate">
{filename || 'script'}
</span>
<span className="shrink-0 font-mono text-[0.625rem] uppercase tracking-wider px-2 py-0.5 rounded-full bg-[rgba(255,255,255,0.06)] text-muted-foreground">
<span className="shrink-0 font-mono text-[0.625rem] uppercase tracking-wider px-2 py-0.5 rounded-full bg-[var(--color-bg-elevated)] text-muted-foreground">
{LANGUAGE_LABELS[language] || language}
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleCopy}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
"bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)]"
)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors border border-border text-foreground hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-elevated)]"
>
{copied ? <Check size={14} className="text-success" /> : <Copy size={14} />}
{copied ? 'Copied' : 'Copy'}
</button>
<button
onClick={onSave}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors",
"bg-success-dim border border-success/20 text-success hover:bg-emerald-500/15"
)}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors bg-accent text-white hover:brightness-110"
>
<BookmarkPlus size={14} />
Save to Library
</button>
<button
onClick={onClose}
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-[var(--color-bg-elevated)] transition-colors"
>
<X size={18} />
</button>
@@ -125,16 +118,13 @@ export function ScriptPreviewModal({
</div>
{/* Footer */}
<div className="flex items-center justify-between px-5 py-3 border-t border-[rgba(255,255,255,0.06)]">
<div className="flex items-center justify-between px-5 py-3 border-t border-border">
<span className="font-mono text-[0.625rem] text-muted-foreground">
{lineCount} line{lineCount !== 1 ? 's' : ''}
</span>
<button
onClick={onClose}
className={cn(
"px-4 py-1.5 rounded-lg text-xs font-medium transition-colors",
"bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)]"
)}
className="px-4 py-1.5 rounded-lg text-xs font-medium transition-colors border border-border text-foreground hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-elevated)]"
>
Close & Return to Chat
</button>

View File

@@ -108,7 +108,7 @@ export function ParameterDetectorStepper({
{/* Progress */}
<div className="flex items-center justify-between">
<p className="text-xs font-medium text-foreground">
Candidate {currentIndex + 1} of {candidates.length}
Variable {currentIndex + 1} of {candidates.length}
</p>
<div className="flex items-center gap-1">
{candidates.map((_, i) => (
@@ -127,7 +127,7 @@ export function ParameterDetectorStepper({
{/* Matched line */}
<div className="rounded-lg bg-black/20 px-3 py-2">
<p className="font-sans text-xs text-amber-400 break-all">
<p className="font-sans text-xs text-warning break-all">
{current.matchedLine}
</p>
<p className="font-sans text-xs text-[0.5rem] text-muted-foreground mt-1">
@@ -145,7 +145,7 @@ export function ParameterDetectorStepper({
placeholder="param_key"
/>
{existingKeys.includes(key) && (
<p className="text-[0.625rem] text-amber-400 mt-0.5">Key already exists consider a different name</p>
<p className="text-[0.625rem] text-warning mt-0.5">Key already exists consider a different name</p>
)}
</div>
<div>
@@ -174,7 +174,7 @@ export function ParameterDetectorStepper({
<select
value={type}
onChange={e => setType(e.target.value as ScriptParameter['type'])}
className="w-full rounded-lg border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(96,165,250,0.3)]"
className="w-full rounded-lg border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(249,115,22,0.25)] focus:ring-1 focus:ring-[rgba(249,115,22,0.1)]"
>
{PARAM_TYPES.map(t => (
<option key={t.value} value={t.value}>{t.label}</option>

View File

@@ -92,7 +92,7 @@ export function ParameterizeAndSavePanel({
if (detected.length > 0) {
setShowStepper(true)
} else {
setDetectionSummary('No parameters detected — script will be saved as-is. Parameter detection currently supports PowerShell only.')
setDetectionSummary('No configurable values found — the script will be saved as-is. Variable detection currently supports PowerShell only.')
}
}, [])
@@ -298,7 +298,7 @@ export function ParameterizeAndSavePanel({
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
Detect Parameters
Find Variables
</button>
</section>
)}
@@ -313,7 +313,7 @@ export function ParameterizeAndSavePanel({
<pre className="p-3 text-xs font-mono text-foreground whitespace-pre-wrap break-all">
{workingScript.split(/({{.*?}})/).map((part, i) =>
/^{{.*}}$/.test(part)
? <span key={i} className="text-amber-400 font-semibold">{part}</span>
? <span key={i} className="text-warning font-semibold">{part}</span>
: <span key={i}>{part}</span>
)}
</pre>
@@ -332,7 +332,7 @@ export function ParameterizeAndSavePanel({
{showStepper && candidates.length > 0 && (
<section className="space-y-2">
<p className="font-sans text-xs uppercase tracking-widest text-muted-foreground">
Detected Parameters
Configurable Variables
</p>
<ParameterDetectorStepper
candidates={candidates}
@@ -348,7 +348,7 @@ export function ParameterizeAndSavePanel({
{parameters.length > 0 && !showStepper && (
<section className="space-y-2">
<p className="font-sans text-xs uppercase tracking-widest text-muted-foreground">
Parameters ({parameters.length})
Variables ({parameters.length})
</p>
<div className="space-y-1">
{parameters.map((p) => (
@@ -357,7 +357,7 @@ export function ParameterizeAndSavePanel({
className="flex items-center justify-between rounded-lg bg-elevated px-3 py-2"
>
<div className="flex items-center gap-2">
<code className="text-xs font-mono text-amber-400">{`{{${p.key}}}`}</code>
<code className="text-xs font-mono text-warning">{`{{${p.key}}}`}</code>
<span className="text-xs text-muted-foreground">{p.label}</span>
</div>
<span className="text-[0.625rem] text-muted-foreground uppercase tracking-wide">

View File

@@ -3,9 +3,9 @@ import { cn } from '@/lib/utils'
import type { ScriptTemplateListItem } from '@/types'
const COMPLEXITY_CLASSES: Record<ScriptTemplateListItem['complexity'], string> = {
beginner: 'text-emerald-400 bg-emerald-400/10',
intermediate: 'text-amber-400 bg-amber-400/10',
advanced: 'text-rose-500 bg-rose-500/10',
beginner: 'text-success bg-success-dim',
intermediate: 'text-warning bg-warning-dim',
advanced: 'text-danger bg-danger-dim',
}
interface Props {
@@ -28,7 +28,7 @@ export function TemplateCard({ template, onConfigure }: Props) {
<div className="flex items-center gap-1.5 shrink-0">
{template.requires_elevation && (
<span title="Requires administrator elevation">
<ShieldAlert size={13} className="text-amber-400" />
<ShieldAlert size={13} className="text-warning" />
</span>
)}
<span className={cn('font-sans text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded', COMPLEXITY_CLASSES[template.complexity])}>
@@ -62,7 +62,7 @@ export function TemplateCard({ template, onConfigure }: Props) {
<button
type="button"
onClick={() => onConfigure(template.id)}
className="shrink-0 bg-primary/10 border border-primary/20 text-primary text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors"
className="shrink-0 bg-accent-dim border border-primary/20 text-accent-text text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors"
>
Configure
</button>

View File

@@ -208,8 +208,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
className={cn(
'flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors',
copiedCommandIndex === index
? 'bg-emerald-400/10 text-emerald-400'
: 'bg-accent text-muted-foreground hover:bg-accent hover:text-foreground'
? 'bg-success-dim text-success'
: 'border border-border bg-[var(--color-bg-elevated)] text-muted-foreground hover:text-foreground hover:border-[var(--color-border-hover)]'
)}
>
{copiedCommandIndex === index ? (

View File

@@ -224,7 +224,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
<button
type="button"
onClick={addCommand}
className="flex items-center gap-1 rounded-md bg-accent px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
className="flex items-center gap-1 rounded-md border border-border bg-[var(--color-bg-elevated)] px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground hover:border-[var(--color-border-hover)]"
>
<Plus className="h-3 w-3" />
Add Command
@@ -304,7 +304,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
<button
type="button"
onClick={addTag}
className="rounded-md bg-accent px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded-md border border-border bg-[var(--color-bg-elevated)] px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[var(--color-border-hover)]"
>
Add
</button>

View File

@@ -226,7 +226,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
'rounded-full px-2.5 py-1 text-xs transition-colors',
selectedTag === tag.tag
? 'bg-primary text-white'
: 'bg-accent text-muted-foreground hover:bg-accent'
: 'border border-border bg-[var(--color-bg-elevated)] text-muted-foreground hover:text-foreground hover:border-[var(--color-border-hover)]'
)}
>
{tag.tag} ({tag.count})