Initial commit - ViewEngine.Console sample
This commit is contained in:
commit
f5d7701fd2
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>
|
||||
Loading…
Reference in New Issue
Block a user