Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ import React from "react";
export default function Footer() {
return (
<footer className="px-6 py-12 text-center text-sm text-gray-600 dark:text-gray-400 mt-24 bg-gray-50 dark:bg-gray-900/40 border-t border-gray-100 dark:border-gray-800">
<div className="max-w-4xl mx-auto mb-6">
<nav className="flex justify-center gap-6 flex-wrap">
<a href="/" className="hover:text-gray-900 dark:hover:text-gray-200 transition-colors">
Home
</a>
<a href="/validator" className="hover:text-gray-900 dark:hover:text-gray-200 transition-colors">
Validator
</a>
<a
href="https://github.com/agentsmd/agents.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
GitHub
</a>
</nav>
</div>
<p>
Copyright © AGENTS.md a Series of LF Projects, LLC
<br />
Expand Down
13 changes: 10 additions & 3 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,21 @@ export default function Hero() {
</p>

<div className="mt-6 flex gap-4 flex-col sm:flex-row w-full sm:w-auto justify-center sm:justify-start">
{/* Primary CTA — scroll to the Examples section */}
{/* Primary CTA — validator */}
<a
href="#examples"
href="/validator"
className="inline-block px-5 py-3 rounded-full bg-black text-white dark:bg-white dark:text-black text-sm font-medium text-center hover:opacity-80"
>
Validate Your AGENTS.md
</a>
{/* Secondary CTA — scroll to the Examples section */}
<a
href="#examples"
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-full border border-gray-300 dark:border-gray-600 text-sm font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800"
>
Explore Examples
</a>
{/* Secondary CTA — view on GitHub */}
{/* Tertiary CTA — view on GitHub */}
<a
href="https://github.com/agentsmd/agents.md"
target="_blank"
Expand Down
33 changes: 33 additions & 0 deletions components/ValidatorInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

interface ValidatorInputProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}

/**
* Textarea component for AGENTS.md input
*/
export default function ValidatorInput({ value, onChange, placeholder }: ValidatorInputProps) {
const lineCount = value.split('\n').length;
const charCount = value.length;

return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-2 text-sm text-gray-600 dark:text-gray-400">
<label className="font-medium">Your AGENTS.md content</label>
<div className="text-xs">
{lineCount} lines · {charCount} characters
</div>
</div>
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="flex-1 w-full p-4 rounded-lg bg-white dark:bg-black text-gray-800 dark:text-gray-100 text-sm font-mono leading-6 border border-gray-200 dark:border-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600 resize-none shadow-sm"
spellCheck={false}
/>
</div>
);
}
90 changes: 90 additions & 0 deletions components/ValidatorPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';

interface ValidatorPreviewProps {
content: string;
}

/**
* Live preview of AGENTS.md content with syntax highlighting
*/
export default function ValidatorPreview({ content }: ValidatorPreviewProps) {
/**
* Very lightly highlight the Markdown without fully parsing it.
*/
function parseMarkdown(md: string): React.ReactNode[] {
const lines = md.split('\n');
const elements: React.ReactNode[] = [];

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

// Handle headers
if (line.startsWith('# ') || line.startsWith('## ') || line.startsWith('### ')) {
elements.push(
<div key={i} className="font-bold">
{line}
</div>
);
} else if (line.startsWith('- ')) {
// Handle list items with inline code
elements.push(<div key={i}>{renderLineWithInlineCode(line)}</div>);
} else if (line.trim() === '') {
// Handle empty lines
elements.push(<div key={i}>&nbsp;</div>);
} else {
// Handle regular lines with inline code
elements.push(<div key={i}>{renderLineWithInlineCode(line)}</div>);
}
}

return elements;
}

/**
* Render a line with inline code highlighting
*/
function renderLineWithInlineCode(line: string): React.ReactNode {
const parts = line.split(/(`[^`]+`)/g);

return parts.map((part, index) => {
if (part.startsWith('`') && part.endsWith('`')) {
// This is inline code
return (
<span key={index} className="bg-gray-200 dark:bg-gray-800 px-1 rounded">
{part}
</span>
);
}
// Regular text
return part;
});
}

if (!content || content.trim().length === 0) {
return (
<div className="flex flex-col h-full">
<div className="mb-2 text-sm text-gray-600 dark:text-gray-400 font-medium">
Preview
</div>
<div className="flex-1 rounded-lg bg-white dark:bg-black border border-gray-200 dark:border-gray-700 p-4 flex items-center justify-center shadow-sm">
<p className="text-gray-400 dark:text-gray-600 text-sm">
Your preview will appear here
</p>
</div>
</div>
);
}

return (
<div className="flex flex-col h-full">
<div className="mb-2 text-sm text-gray-600 dark:text-gray-400 font-medium">
Preview
</div>
<div className="flex-1 overflow-y-auto rounded-lg bg-white dark:bg-black border border-gray-200 dark:border-gray-700 shadow-sm">
<pre className="p-4 text-xs leading-6 text-gray-800 dark:text-gray-100">
<code>{parseMarkdown(content)}</code>
</pre>
</div>
</div>
);
}
219 changes: 219 additions & 0 deletions components/ValidatorResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import React from 'react';
import { ValidationSuggestion } from '@/lib/validationRules';
import CopyIcon from './icons/CopyIcon';

interface ValidatorResultsProps {
suggestions: ValidationSuggestion[];
score: number;
summary: string;
}

/**
* Display validation results with suggestions
*/
export default function ValidatorResults({ suggestions, score, summary }: ValidatorResultsProps) {
const [expandedIndex, setExpandedIndex] = React.useState<number | null>(null);
const [copiedIndex, setCopiedIndex] = React.useState<number | null>(null);

const toggleExpand = (index: number) => {
setExpandedIndex(expandedIndex === index ? null : index);
};

const copyExample = async (example: string, index: number) => {
try {
await navigator.clipboard.writeText(example);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};

// Determine score color
const getScoreColor = (score: number) => {
if (score >= 80) return 'text-green-600 dark:text-green-400';
if (score >= 60) return 'text-yellow-600 dark:text-yellow-400';
return 'text-orange-600 dark:text-orange-400';
};

// Determine score background
const getScoreBg = (score: number) => {
if (score >= 80) return 'bg-green-50 dark:bg-green-900/20';
if (score >= 60) return 'bg-yellow-50 dark:bg-yellow-900/20';
return 'bg-orange-50 dark:bg-orange-900/20';
};

return (
<div className="flex flex-col h-full">
{/* Score and summary */}
<div className={`p-4 rounded-lg mb-4 ${getScoreBg(score)}`}>
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Validation Results
</h3>
<div className={`text-2xl font-bold ${getScoreColor(score)}`}>
{score}/100
</div>
</div>
<p className="text-sm text-gray-700 dark:text-gray-300">{summary}</p>
</div>

{/* Suggestions list */}
<div className="flex-1 overflow-y-auto space-y-3">
{suggestions.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<p>Paste your AGENTS.md content to see suggestions.</p>
</div>
) : (
suggestions.map((suggestion, index) => (
<div
key={index}
className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-white dark:bg-gray-900"
>
<button
onClick={() => toggleExpand(index)}
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<div className="flex items-start gap-3">
{/* Icon based on type */}
<div className="flex-shrink-0 mt-0.5">
{suggestion.type === 'success' ? (
<svg
className="w-5 h-5 text-green-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
) : suggestion.type === 'info' ? (
<svg
className="w-5 h-5 text-blue-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
) : (
<svg
className="w-5 h-5 text-yellow-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
/>
</svg>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
{suggestion.title}
</h4>
<span
className={`text-xs px-2 py-0.5 rounded ${
suggestion.priority === 'high'
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
: suggestion.priority === 'medium'
? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
}`}
>
{suggestion.priority}
</span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
{suggestion.message}
</p>
</div>
<div className="flex-shrink-0">
<svg
className={`w-5 h-5 text-gray-400 transition-transform ${
expandedIndex === index ? 'rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</div>
</button>

{/* Expanded content */}
{expandedIndex === index && (suggestion.suggestion || suggestion.example) && (
<div className="px-4 pb-4 pt-0 border-t border-gray-100 dark:border-gray-800">
{suggestion.suggestion && (
<div className="mt-3">
<p className="text-sm text-gray-700 dark:text-gray-300">
<strong className="text-gray-900 dark:text-gray-100">Suggestion:</strong>{' '}
{suggestion.suggestion}
</p>
</div>
)}
{suggestion.example && (
<div className="mt-3">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300">
Example:
</span>
<button
onClick={() => copyExample(suggestion.example!, index)}
className="p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label="Copy example"
>
{copiedIndex === index ? (
<svg
className="w-4 h-4 text-green-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
) : (
<CopyIcon className="w-4 h-4 text-gray-500" />
)}
</button>
</div>
<pre className="text-xs bg-gray-50 dark:bg-black p-3 rounded border border-gray-200 dark:border-gray-700 overflow-x-auto">
<code className="text-gray-800 dark:text-gray-200">{suggestion.example}</code>
</pre>
</div>
)}
</div>
)}
</div>
))
)}
</div>
</div>
);
}
Loading