File size: 3,345 Bytes
63458bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import "@std/dotenv/load";
import { Application, Router } from "@oak/oak";
import { DocsExtractorAgent } from "./main.ts";

const jobs = new Map<string, { status: string; result?: unknown; error?: string }>();

const router = new Router();

router
  .post("/api/extract", async (ctx) => {
    try {
      const body = await ctx.request.body.json();
      const { docsUrl, extractionType } = body;

      if (!docsUrl || !extractionType) {
        ctx.response.status = 400;
        ctx.response.body = { error: "Missing required fields: docsUrl, extractionType" };
        return;
      }

      const jobId = crypto.randomUUID();
      jobs.set(jobId, { status: "processing" });

      // Process extraction in background
      (async () => {
        try {
          const agent = await DocsExtractorAgent.create();
          const result = await agent.extract(docsUrl, extractionType, "claude-sonnet-4-20250514");
          jobs.set(jobId, { status: "completed", result });
          await agent.cleanup();
        } catch (error) {
          console.error("Extraction error:", error);
          jobs.set(jobId, { 
            status: "failed", 
            error: error instanceof Error ? error.message : String(error)
          });
        }
      })();

      ctx.response.body = { jobId, status: "processing" };
    } catch (error) {
      console.error("API error:", error);
      ctx.response.status = 500;
      ctx.response.body = { 
        error: error instanceof Error ? error.message : "Internal server error" 
      };
    }
  })
  .get("/api/job/:jobId", (ctx) => {
    const jobId = ctx.params.jobId;
    const job = jobs.get(jobId);

    if (!job) {
      ctx.response.status = 404;
      ctx.response.body = { error: "Job not found" };
      return;
    }

    ctx.response.body = job;
  })
  .get("/api/docs", async (ctx) => {
    try {
      const docs: Array<{ name: string; path: string }> = [];
      for await (const entry of Deno.readDir("./generated-docs")) {
        if (entry.isFile && entry.name.endsWith(".md")) {
          docs.push({ name: entry.name, path: `./generated-docs/${entry.name}` });
        }
      }
      ctx.response.body = { docs };
    } catch (error) {
      console.error("Error reading docs:", error);
      ctx.response.body = { docs: [] };
    }
  })
  .get("/api/docs/:filename", async (ctx) => {
    try {
      const filename = ctx.params.filename;
      const content = await Deno.readTextFile(`./generated-docs/${filename}`);
      ctx.response.headers.set("Content-Type", "text/markdown");
      ctx.response.body = content;
    } catch (error) {
      ctx.response.status = 404;
      ctx.response.body = { error: "Document not found" };
    }
  });

const app = new Application();

// CORS middleware
app.use(async (ctx, next) => {
  ctx.response.headers.set("Access-Control-Allow-Origin", "*");
  ctx.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  ctx.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if (ctx.request.method === "OPTIONS") {
    ctx.response.status = 200;
    return;
  }
  await next();
});

app.use(router.routes());
app.use(router.allowedMethods());

const PORT = 8000;
console.log(`🚀 Docs Extractor API running on http://localhost:${PORT}`);
await app.listen({ port: PORT });