🔗 Add URL Content Extraction Feature (#514)

* feat: add URL content extraction for AI diagram generation

* Changes made as recommended by Claude:

1. Added a request timeout to prevent server resources from being tied up (route.ts)
2. Implemented runtime validation for the API response shape (url-utils.ts)
3. Removed hardcoded English error messages and replaced them with localized strings (url-input-dialog.tsx)
4. Fixed the incorrect i18n namespace (changed from pdf.* to url.*) (url-input-dialog.tsx and en/ja/zh.json)

* chore: restore package.json and package-lock.json

* fix: use i18n strings for URL dialog error messages

---------

Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
This commit is contained in:
Biki Kalita
2026-01-05 20:53:50 +05:30
committed by GitHub
parent 625d8f2afe
commit 6326f9dec6
11 changed files with 837 additions and 9 deletions

View File

@@ -51,7 +51,8 @@
"badResponse": "Bad response",
"clickToEdit": "Click to edit",
"editMessage": "Edit message",
"saveAndSubmit": "Save & Submit"
"saveAndSubmit": "Save & Submit",
"ExtractURL": "Extract from URL"
},
"examples": {
"title": "Create diagrams with AI",
@@ -186,6 +187,15 @@
"chars": "chars",
"removeFile": "Remove file"
},
"url": {
"title": "Extract Content from URL",
"description": "Paste a URL to extract and analyze its content",
"Extracting": "Extracting...",
"extract": "Extract",
"Cancel": "Cancel",
"enterUrl": "Please enter a URL",
"invalidFormat": "Invalid URL format"
},
"reasoning": {
"thinking": "Thinking...",
"thoughtFor": "Thought for {duration} seconds",

View File

@@ -51,7 +51,8 @@
"badResponse": "悪い応答",
"clickToEdit": "クリックして編集",
"editMessage": "メッセージを編集",
"saveAndSubmit": "保存して送信"
"saveAndSubmit": "保存して送信",
"ExtractURL": "URLから抽出"
},
"examples": {
"title": "AI でダイアグラムを作成",
@@ -186,6 +187,15 @@
"chars": "文字",
"removeFile": "ファイルを削除"
},
"url": {
"title": "URLからコンテンツを抽出",
"description": "URLを貼り付けてそのコンテンツを抽出および分析します",
"Extracting": "抽出中...",
"extract": "抽出",
"Cancel": "キャンセル",
"enterUrl": "URLを入力してください",
"invalidFormat": "無効なURL形式です"
},
"reasoning": {
"thinking": "考え中...",
"thoughtFor": "{duration} 秒考えました",

View File

@@ -51,7 +51,8 @@
"badResponse": "无帮助",
"clickToEdit": "点击编辑",
"editMessage": "编辑消息",
"saveAndSubmit": "保存并提交"
"saveAndSubmit": "保存并提交",
"ExtractURL": "从 URL 提取"
},
"examples": {
"title": "用 AI 创建图表",
@@ -186,6 +187,15 @@
"chars": "字符",
"removeFile": "移除文件"
},
"url": {
"title": "从 URL 提取内容",
"description": "粘贴 URL 以提取和分析其内容",
"Extracting": "提取中...",
"extract": "提取",
"Cancel": "取消",
"enterUrl": "请输入 URL",
"invalidFormat": "URL 格式无效"
},
"reasoning": {
"thinking": "思考中...",
"thoughtFor": "思考了 {duration} 秒",

49
lib/url-utils.ts Normal file
View File

@@ -0,0 +1,49 @@
import { z } from "zod"
export interface UrlData {
url: string
title: string
content: string
charCount: number
isExtracting: boolean
}
const UrlResponseSchema = z.object({
title: z.string().default("Untitled"),
content: z.string(),
charCount: z.number().int().nonnegative(),
})
export async function extractUrlContent(url: string): Promise<UrlData> {
const response = await fetch("/api/parse-url", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
})
// Try to parse JSON once
const raw = await response
.json()
.catch(() => ({ error: "Unexpected non-JSON response" }))
if (!response.ok) {
const message =
typeof raw === "object" && raw && "error" in raw
? String((raw as any).error)
: "Failed to extract URL content"
throw new Error(message)
}
const parsed = UrlResponseSchema.safeParse(raw)
if (!parsed.success) {
throw new Error("Malformed response from URL extraction API")
}
return {
url,
title: parsed.data.title,
content: parsed.data.content,
charCount: parsed.data.charCount,
isExtracting: false,
}
}