Initial commit - ViewEngine.Console sample
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -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
|
||||||
226
Program.cs
Executable file
226
Program.cs
Executable file
@@ -0,0 +1,226 @@
|
|||||||
|
using ViewEngine.Client;
|
||||||
|
using ViewEngine.Client.Models;
|
||||||
|
|
||||||
|
namespace ViewEngine.Console;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Demo console application showing how to use the ViewEngine.Client library
|
||||||
|
/// </summary>
|
||||||
|
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 <api-key>");
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
159
README.md
Executable file
159
README.md
Executable file
@@ -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 <your-api-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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": "<html>...</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 <your-api-key>` 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.
|
||||||
14
ViewEngine.Console.csproj
Normal file
14
ViewEngine.Console.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ViewEngine.Client" Version="1.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user