Files
wehub-resource-sync 0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

361 lines
12 KiB
TypeScript

import React, { useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import styles from './A2ATaskSimulator.module.css';
interface TaskArtifact {
name: string;
description: string;
parts: {
content: string;
type: string;
}[];
index: number;
append: boolean;
lastChunk: boolean;
}
interface Task {
id: string;
sessionId?: string;
status: {
state: 'submitted' | 'working' | 'input-required' | 'completed' | 'canceled' | 'failed';
message: string;
timestamp: string;
};
artifacts: TaskArtifact[];
history: {
state: Task['status']['state'];
message: string;
timestamp: string;
}[];
metadata: Record<string, unknown>;
}
type TaskKey = 'lead-qualification' | 'content-moderation';
type TaskState = Task['status']['state'];
const sampleTasks: Record<TaskKey, Task> = {
'lead-qualification': {
id: 'task-001',
sessionId: 'session-001',
status: {
state: 'submitted',
message: 'Task received, awaiting processing',
timestamp: new Date().toISOString(),
},
artifacts: [],
history: [],
metadata: {
priority: 'high',
source: 'sales-pipeline',
},
},
'content-moderation': {
id: 'task-002',
sessionId: 'session-001',
status: {
state: 'submitted',
message: 'Task received',
timestamp: new Date().toISOString(),
},
artifacts: [],
history: [],
metadata: {
priority: 'high',
contentType: 'social-post',
platform: 'community-forum',
},
},
};
const sampleTaskEntries = Object.entries(sampleTasks) as Array<[TaskKey, Task]>;
const taskStateSequences: Record<
TaskKey,
Array<{ state: TaskState; message: string; duration: number }>
> = {
'lead-qualification': [
{ state: 'submitted', message: 'New lead qualification task received', duration: 2000 },
{ state: 'working', message: 'Analyzing lead data and engagement metrics', duration: 3000 },
{ state: 'input-required', message: 'Need additional contact information', duration: 2500 },
{ state: 'working', message: 'Processing updated lead information', duration: 2000 },
{
state: 'completed',
message: 'Lead qualification completed with high confidence',
duration: 0,
},
],
'content-moderation': [
{ state: 'submitted', message: 'Content moderation request received', duration: 2000 },
{ state: 'working', message: 'Scanning content for policy violations', duration: 2500 },
{ state: 'working', message: 'Performing sentiment analysis', duration: 2000 },
{ state: 'input-required', message: 'Need human review for ambiguous content', duration: 2500 },
{ state: 'working', message: 'Applying moderation decision', duration: 2000 },
{ state: 'completed', message: 'Content moderation completed with action taken', duration: 0 },
],
};
const stateColors: Record<TaskState, string> = {
submitted: '#60a5fa',
working: '#f59e0b',
'input-required': '#7c3aed',
completed: '#10b981',
canceled: '#6b7280',
failed: '#ef4444',
};
const stateDescriptions: Record<TaskState, string> = {
submitted: 'Task has been received but not yet started',
working: 'Task is actively being processed',
'input-required': 'Additional information needed from client',
completed: 'Task has been successfully completed',
canceled: 'Task was terminated before completion',
failed: 'Task encountered an unrecoverable error',
};
export default function A2ATaskSimulator() {
const [selectedTask, setSelectedTask] = useState<Task>(sampleTasks['lead-qualification']);
const [isAnimating, setIsAnimating] = useState(false);
const resetTask = (taskKey: TaskKey) => {
const task = { ...sampleTasks[taskKey] };
task.status = {
state: 'submitted',
message: 'Task received, awaiting processing',
timestamp: new Date().toISOString(),
};
task.artifacts = [];
task.history = [];
return task;
};
const handleTaskSelect = (taskKey: TaskKey) => {
if (isAnimating) {
return;
}
setSelectedTask(resetTask(taskKey));
};
const addHistoryEntry = (task: Task, newState: TaskState, message: string) => {
return {
...task,
history: [
{
state: task.status.state,
message: task.status.message,
timestamp: task.status.timestamp,
},
...task.history,
],
status: {
state: newState,
message,
timestamp: new Date().toISOString(),
},
};
};
const generateArtifact = (task: Task) => {
if (task.status.state !== 'completed') {
return task;
}
const artifactContent =
task.id === 'task-001'
? {
name: 'lead-analysis',
description: 'Lead qualification analysis results',
content: 'Lead scored 85/100 based on engagement metrics and profile completeness',
}
: {
name: 'moderation-report',
description: 'Content moderation analysis and action taken',
content:
'Content flagged for community guidelines violation. Toxicity score: 0.82. Action taken: Content hidden and user warned. Escalated to human moderator for review.',
};
const newArtifact: TaskArtifact = {
name: artifactContent.name,
description: artifactContent.description,
parts: [
{
content: artifactContent.content,
type: 'text/plain',
},
],
index: task.artifacts.length,
append: false,
lastChunk: true,
};
return {
...task,
artifacts: [...task.artifacts, newArtifact],
};
};
const startTaskAnimation = async () => {
if (isAnimating) {
return;
}
setIsAnimating(true);
const taskKey =
sampleTaskEntries.find(([, task]) => task.id === selectedTask.id)?.[0] ??
'lead-qualification';
const sequence = taskStateSequences[taskKey];
for (let i = 0; i < sequence.length; i++) {
const { state, message, duration } = sequence[i];
await new Promise((resolve) => setTimeout(resolve, duration));
setSelectedTask((prev) => {
const updated = addHistoryEntry(prev, state, message);
return generateArtifact(updated);
});
}
setIsAnimating(false);
};
return (
<div className={styles.container}>
<div className={styles.taskSelector}>
<h4>Select Task Type to See Lifecycle</h4>
<div className={styles.taskButtons}>
{sampleTaskEntries.map(([key, task]) => (
<button
key={key}
className={`${styles.taskButton} ${selectedTask.id === task.id ? styles.active : ''}`}
onClick={() => handleTaskSelect(key)}
disabled={isAnimating}
>
{key
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')}
</button>
))}
</div>
<button className={styles.startButton} onClick={startTaskAnimation} disabled={isAnimating}>
{isAnimating ? 'Running...' : 'Start'}
</button>
</div>
<div className={styles.simulator}>
<div className={styles.stateVisualizer}>
<h5>Task Lifecycle States</h5>
<div className={styles.states}>
{(Object.keys(stateDescriptions) as TaskState[]).map((state) => (
<div
key={state}
className={`${styles.state} ${
selectedTask.status.state === state ? styles.active : ''
}`}
style={
{
'--state-color': stateColors[state],
} as React.CSSProperties
}
>
<div className={styles.stateHeader}>
<span className={styles.stateDot} />
<span className={styles.stateName}>
{state
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')}
</span>
</div>
<p className={styles.stateDescription}>{stateDescriptions[state]}</p>
</div>
))}
</div>
</div>
<div className={styles.taskDetails}>
<div className={styles.section}>
<h5>Current Status</h5>
<div className={styles.statusCard}>
<div className={styles.statusHeader}>
<span
className={styles.statusDot}
style={{ backgroundColor: stateColors[selectedTask.status.state] }}
/>
<span className={styles.statusState}>
{selectedTask.status.state
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')}
</span>
</div>
<p className={styles.statusMessage}>{selectedTask.status.message}</p>
<span className={styles.statusTime}>
{new Date(selectedTask.status.timestamp).toLocaleTimeString()}
</span>
</div>
</div>
<div className={styles.section}>
<h5>History</h5>
<div className={styles.history}>
{selectedTask.history.map((entry, index) => (
<div key={index} className={styles.historyEntry}>
<div className={styles.historyHeader}>
<span
className={styles.historyDot}
style={{ backgroundColor: stateColors[entry.state] }}
/>
<span className={styles.historyState}>
{entry.state
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')}
</span>
</div>
<p className={styles.historyMessage}>{entry.message}</p>
<span className={styles.historyTime}>
{new Date(entry.timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
</div>
<AnimatePresence>
{selectedTask.artifacts.length > 0 && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className={styles.section}
>
<h5>Generated Artifacts</h5>
<div className={styles.artifacts}>
{selectedTask.artifacts.map((artifact, index) => (
<div key={index} className={styles.artifact}>
<div className={styles.artifactHeader}>
<span className={styles.artifactName}>{artifact.name}</span>
<span className={styles.artifactIndex}>#{artifact.index + 1}</span>
</div>
<p className={styles.artifactDescription}>{artifact.description}</p>
{artifact.parts.map((part, partIndex) => (
<div key={partIndex} className={styles.artifactPart}>
<span className={styles.partType}>{part.type}</span>
<div className={styles.partContent}>{part.content}</div>
</div>
))}
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
);
}