commit f5d7701fd29c9c4dfebfbd35a5138b13601782ae Author: David Friedel Date: Sun Dec 28 18:25:44 2025 +0000 Initial commit - ViewEngine.Console sample diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d3f180 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Build results +bin/ +obj/ +[Dd]ebug/ +[Rr]elease/ + +# Visual Studio +.vs/ +*.user +*.suo + +# JetBrains Rider +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# User secrets +appsettings.*.json +!appsettings.json diff --git a/Program.cs b/Program.cs new file mode 100755 index 0000000..8231c7d --- /dev/null +++ b/Program.cs @@ -0,0 +1,226 @@ +using ViewEngine.Client; +using ViewEngine.Client.Models; + +namespace ViewEngine.Console; + +/// +/// Demo console application showing how to use the ViewEngine.Client library +/// +class Program +{ + static async Task Main(string[] args) + { + System.Console.WriteLine("╔══════════════════════════════════════════════════════════╗"); + System.Console.WriteLine("║ ViewEngine Client Library Demo ║"); + System.Console.WriteLine("║ Demonstrates using ViewEngine.Client NuGet package ║"); + System.Console.WriteLine("╚══════════════════════════════════════════════════════════╝"); + System.Console.WriteLine(); + + // Get API key from command line or prompt + string? apiKey; + if (args.Length > 0) + { + apiKey = args[0]; + } + else + { + System.Console.Write("Enter your API key: "); + apiKey = System.Console.ReadLine(); + } + + if (string.IsNullOrWhiteSpace(apiKey)) + { + System.Console.WriteLine("❌ Error: API key is required"); + System.Console.WriteLine(); + System.Console.WriteLine("Usage:"); + System.Console.WriteLine(" dotnet run "); + System.Console.WriteLine(" OR"); + System.Console.WriteLine(" dotnet run (you'll be prompted for the API key)"); + return; + } + + try + { + await RunDemoAsync(apiKey); + } + catch (Exception ex) + { + System.Console.WriteLine($"❌ Error: {ex.Message}"); + System.Console.WriteLine(); + System.Console.WriteLine("Stack trace:"); + System.Console.WriteLine(ex.StackTrace); + } + + System.Console.WriteLine(); + System.Console.WriteLine("Press any key to exit..."); + System.Console.ReadKey(); + } + + static async Task RunDemoAsync(string apiKey) + { + // Create the ViewEngine client + using var client = new ViewEngineClient(apiKey); + + System.Console.WriteLine("🔍 Step 1: Discovering available MCP tools..."); + System.Console.WriteLine(); + + try + { + var tools = await client.ListToolsAsync(); + System.Console.WriteLine($"✅ Tools response received"); + System.Console.WriteLine($" {tools}"); + } + catch (Exception ex) + { + System.Console.WriteLine($"⚠️ Could not list tools: {ex.Message}"); + } + + System.Console.WriteLine(); + System.Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.Console.WriteLine(); + + // Get URL to retrieve + System.Console.Write("Enter a URL to retrieve (or press Enter for example.com): "); + var url = System.Console.ReadLine(); + if (string.IsNullOrWhiteSpace(url)) + { + url = "https://example.com"; + } + + System.Console.WriteLine(); + System.Console.Write("Force fresh retrieval? (y/n, default: n - use cache if available): "); + var forceRefreshInput = System.Console.ReadLine()?.ToLower(); + var forceRefresh = forceRefreshInput == "y"; + + System.Console.WriteLine(); + System.Console.Write("Generate AI summary? (y/n, default: n): "); + var summaryInput = System.Console.ReadLine()?.ToLower(); + var generateSummary = summaryInput == "y"; + + System.Console.WriteLine(); + System.Console.WriteLine($"🌐 Step 2: Submitting retrieval request for {url}..."); + if (forceRefresh) + { + System.Console.WriteLine(" (Forcing fresh retrieval, bypassing cache)"); + } + if (generateSummary) + { + System.Console.WriteLine(" (AI summary will be generated)"); + } + System.Console.WriteLine(); + + // Submit the retrieval request + var request = new SubmitRetrievalRequest + { + Url = url, + ForceRefresh = forceRefresh, + GenerateSummary = generateSummary, + TimeoutSeconds = 60, + Priority = 5 + }; + + var submitResponse = await client.SubmitRetrievalAsync(request); + + System.Console.WriteLine($"✅ Request submitted successfully!"); + System.Console.WriteLine($" Request ID: {submitResponse.RequestId}"); + System.Console.WriteLine($" Status: {submitResponse.Status}"); + System.Console.WriteLine($" Estimated wait: {submitResponse.EstimatedWaitTimeSeconds}s"); + + System.Console.WriteLine(); + System.Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.Console.WriteLine(); + System.Console.WriteLine("⏳ Step 3: Polling for results..."); + System.Console.WriteLine(); + + // Poll for completion + RetrievalStatusResponse status; + int attempt = 0; + const int maxAttempts = 60; + + do + { + attempt++; + await Task.Delay(2000); + + status = await client.GetRetrievalStatusAsync(submitResponse.RequestId); + System.Console.WriteLine($" [{attempt}/{maxAttempts}] Status: {status.Status} - {status.Message}"); + + if (status.Status == "failed" || status.Status == "canceled") + { + System.Console.WriteLine($" Error: {status.Error}"); + return; + } + + } while (status.Status != "complete" && attempt < maxAttempts); + + if (status.Status != "complete") + { + System.Console.WriteLine("⚠️ Timeout: Maximum polling attempts reached"); + return; + } + + System.Console.WriteLine(); + System.Console.WriteLine($"✅ Retrieval completed!"); + System.Console.WriteLine($" URL: {status.Url}"); + System.Console.WriteLine($" Completed at: {status.CompletedAt}"); + + // Download the page content + System.Console.WriteLine(); + System.Console.WriteLine("⬇️ Step 4: Downloading page content..."); + + var pageData = await client.GetPageContentAsync(submitResponse.RequestId); + + System.Console.WriteLine(); + System.Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.Console.WriteLine("📄 Page Content:"); + System.Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.Console.WriteLine($" Title: {pageData.Title}"); + System.Console.WriteLine($" URL: {pageData.Url}"); + System.Console.WriteLine($" Meta Description: {pageData.MetaDescription ?? "(none)"}"); + System.Console.WriteLine($" Favicon: {pageData.FaviconUrl ?? "(none)"}"); + System.Console.WriteLine($" Routes: {pageData.Routes?.Count ?? 0} links"); + System.Console.WriteLine($" Body Routes: {pageData.BodyRoutes?.Count ?? 0} links"); + + // Display HTTP status code (new feature!) + if (pageData.HttpStatusCode.HasValue) + { + System.Console.WriteLine($" HTTP Status: {pageData.HttpStatusCode}"); + if (!pageData.IsSuccess) + { + System.Console.WriteLine($" ⚠️ Warning: Page returned error status"); + System.Console.WriteLine($" IsClientError (4xx): {pageData.IsClientError}"); + System.Console.WriteLine($" IsServerError (5xx): {pageData.IsServerError}"); + } + } + + if (!string.IsNullOrEmpty(pageData.Summary)) + { + System.Console.WriteLine(); + System.Console.WriteLine("🤖 AI Summary:"); + System.Console.WriteLine($" {pageData.Summary}"); + } + + System.Console.WriteLine(); + System.Console.WriteLine("📝 Body preview (first 300 chars):"); + var bodyPreview = pageData.Body?.Length > 300 + ? pageData.Body.Substring(0, 300) + "..." + : pageData.Body ?? "(empty)"; + System.Console.WriteLine($" {bodyPreview}"); + + if (pageData.Routes?.Count > 0) + { + System.Console.WriteLine(); + System.Console.WriteLine("🔗 Top 5 Routes:"); + foreach (var route in pageData.Routes.Take(5)) + { + var adFlag = route.IsPotentialAd ? " [AD]" : ""; + System.Console.WriteLine($" • {route.Text}: {route.Url}{adFlag}"); + } + } + + System.Console.WriteLine(); + System.Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + System.Console.WriteLine(); + System.Console.WriteLine("✅ Demo completed successfully!"); + } +} diff --git a/README.md b/README.md new file mode 100755 index 0000000..f2c9f72 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# ViewEngine Console Demo + +This console application demonstrates how to use the ViewEngine REST API with an API key. + +## What it does + +1. **Discovers MCP Tools** - Calls `GET /mcp/tools` to list available endpoints +2. **Submits Retrieve Request** - Calls `POST /mcp/retrieve` to start a web page retrieval job +3. **Polls for Results** - Calls `GET /mcp/retrieve/{requestId}` repeatedly until the job completes +4. **Downloads Content** - Optionally downloads the retrieved page data + +## Prerequisites + +1. **ViewEngine.API must be running** on `http://localhost:5072` +2. **You need an API key** from the web application: + - Log in to the web app at http://localhost:5072 + - Go to Settings → API Keys + - Create a new API key + - Copy the key (it's only shown once!) + +## How to Run + +### Option 1: With command-line argument + +```bash +cd ViewEngine.Console +dotnet run +``` + +### Option 2: Interactive mode + +```bash +cd ViewEngine.Console +dotnet run +``` + +You'll be prompted to enter your API key. + +## Example Output + +``` +╔══════════════════════════════════════════════════════════╗ +║ ViewEngine REST API Demo ║ +║ Demonstrates using the MCP endpoints with an API key ║ +╚══════════════════════════════════════════════════════════╝ + +Enter your API key: ak_******************************** + +🔍 Step 1: Discovering available MCP tools... + +✅ Found 2 available tools: + • retrieve_url: Retrieve a web page and extract its content... + • get_retrieve_status: Check the status of a retrieval job... + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Enter a URL to retrieve (or press Enter for example.com): + +🌐 Step 2: Submitting retrieval request for https://example.com... + +✅ Request submitted successfully! + Request ID: 12345678-1234-1234-1234-123456789012 + Status: queued + Estimated wait: 30s + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +⏳ Step 3: Polling for results (this may take a while)... + + [1/60] Status: queued - Retrieval queued. Waiting for available feeder. + [2/60] Status: processing - Retrieval in progress... + [3/60] Status: complete - Retrieval completed successfully. + +✅ Retrieval completed! + Status: complete + URL: https://example.com + Completed at: 2025-01-15 10:30:45 + +📄 Content available: + Page Data URL: https://storage.example.com/... + Content Hash: abc123... + Artifacts: screenshot, thumbnail + Metrics: load_time_ms, dom_size + +Download page content? (y/n): y + +⬇️ Downloading page content... + +📄 Page Content (first 500 chars): +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{ + "url": "https://example.com", + "title": "Example Domain", + "text_content": "This domain is for use in illustrative examples...", + "html": "...", + ... +} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +✅ Demo completed successfully! + +Press any key to exit... +``` + +## API Endpoints Used + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/v1/mcp/tools` | GET | List available MCP tools | +| `/v1/mcp/retrieve` | POST | Submit a page retrieval request | +| `/v1/mcp/retrieve/{id}` | GET | Get retrieval status and results | + +## Authentication + +All requests include the API key in the `X-API-Key` header: + +```http +X-API-Key: ak_your-api-key-here +``` + +## Troubleshooting + +### "API key is required" +You forgot to provide an API key. Run with `dotnet run ` or enter it when prompted. + +### "Error: Unauthorized" +Your API key is invalid or has been revoked. Create a new one in the web app. + +### "API not responding" +Make sure ViewEngine.API is running on http://localhost:5072 + +### "Timeout: Maximum polling attempts reached" +The retrieval job took longer than expected. This usually means: +- No feeder clients are online to process the job +- The feeder client crashed or couldn't complete the job +- Check the API logs for more details + +## Code Structure + +The console app is organized into clear methods: + +- `Main()` - Entry point, handles API key input +- `RunDemoAsync()` - Orchestrates the demo flow +- `GetMcpToolsAsync()` - Calls GET /v1/mcp/tools +- `SubmitRetrieveRequestAsync()` - Calls POST /v1/mcp/retrieve +- `PollForResultsAsync()` - Repeatedly calls GET /v1/mcp/retrieve/{id} +- `DownloadPageDataAsync()` - Downloads the retrieved content + +## Next Steps + +This demo shows the MCP endpoints, but ViewEngine also has: + +- **Ingest API** (`/v1/ingest/*`) - Submit retrieval jobs programmatically +- **Feeder API** (`/v1/feeders/*`) - For feeder client applications +- **Billing API** (`/v1/billing/*`) - Check earnings and pricing + +See the API documentation for more details. diff --git a/ViewEngine.Console.csproj b/ViewEngine.Console.csproj new file mode 100644 index 0000000..16469c4 --- /dev/null +++ b/ViewEngine.Console.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + +