Skip to content

Commit adbf0e7

Browse files
authored
Merge pull request #3 from NeverlandYao/main
feat: Add Jina AI service integration and web fragment input function…
2 parents 747d34d + 40cea64 commit adbf0e7

8 files changed

Lines changed: 1171 additions & 14 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ yarn-error.log*
3232

3333
# env files (can opt-in for committing if needed)
3434
.env
35+
.env.local
36+
.env.development.local
37+
.env.test.local
38+
.env.production.local
3539

3640
# vercel
3741
.vercel

app/api/jina/extract/route.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Jina AI 网页内容提取 API 路由
3+
*/
4+
5+
import { NextRequest, NextResponse } from 'next/server'
6+
import { jinaService } from '../../../../lib/jina-service'
7+
import { WebContentExtractRequest } from '../../../../lib/jina-service'
8+
9+
export async function POST(request: NextRequest) {
10+
try {
11+
const body = await request.json()
12+
const { url, userPrompt, extractMode = 'full', language = 'zh' } = body
13+
14+
// 验证必需参数
15+
if (!url) {
16+
return NextResponse.json(
17+
{ success: false, error: '缺少必需参数: url' },
18+
{ status: 400 }
19+
)
20+
}
21+
22+
// 验证URL格式
23+
try {
24+
new URL(url)
25+
} catch {
26+
return NextResponse.json(
27+
{ success: false, error: '无效的URL格式' },
28+
{ status: 400 }
29+
)
30+
}
31+
32+
// 构建请求参数
33+
const extractRequest: WebContentExtractRequest = {
34+
url,
35+
userPrompt,
36+
extractMode,
37+
language
38+
}
39+
40+
// 调用Jina服务提取内容
41+
const result = await jinaService.extractWebContent(extractRequest)
42+
43+
if (!result) {
44+
return NextResponse.json(
45+
{ success: false, error: '网页内容提取失败' },
46+
{ status: 500 }
47+
)
48+
}
49+
50+
return NextResponse.json({
51+
success: true,
52+
data: result
53+
})
54+
55+
} catch (error) {
56+
console.error('Jina extract API error:', error)
57+
return NextResponse.json(
58+
{ success: false, error: '服务器内部错误' },
59+
{ status: 500 }
60+
)
61+
}
62+
}
63+
64+
export async function GET(request: NextRequest) {
65+
try {
66+
const { searchParams } = new URL(request.url)
67+
const action = searchParams.get('action')
68+
69+
if (action === 'test') {
70+
// 测试Jina服务连接
71+
const testUrl = 'https://example.com'
72+
const testResult = await jinaService.parseWebPage(testUrl)
73+
74+
return NextResponse.json({
75+
success: true,
76+
data: {
77+
service: 'Jina AI',
78+
status: testResult.success ? 'connected' : 'error',
79+
error: testResult.error
80+
}
81+
})
82+
}
83+
84+
if (action === 'config') {
85+
// 返回服务配置信息
86+
return NextResponse.json({
87+
success: true,
88+
data: {
89+
readerApiEnabled: true,
90+
deepSearchEnabled: !!process.env.JINA_API_KEY,
91+
supportedModes: ['full', 'summary', 'custom'],
92+
maxContentLength: 8000,
93+
rateLimits: {
94+
reader: '20 requests/minute (free), 500 requests/minute (paid)',
95+
deepSearch: '50 requests/minute'
96+
}
97+
}
98+
})
99+
}
100+
101+
return NextResponse.json(
102+
{ success: false, error: '无效的操作' },
103+
{ status: 400 }
104+
)
105+
106+
} catch (error) {
107+
console.error('Jina config API error:', error)
108+
return NextResponse.json(
109+
{ success: false, error: '获取配置失败' },
110+
{ status: 500 }
111+
)
112+
}
113+
}

app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default function RootLayout({
2929
<html lang="en">
3030
<body
3131
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
32+
suppressHydrationWarning={true}
3233
>
3334
<StoreInitializer>
3435
<SidebarProvider>

components/fragment-input.tsx

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
import { useState } from "react"
44
import { Textarea } from "@/components/ui/textarea"
55
import { Button } from "@/components/ui/button"
6+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
7+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
8+
import { FileText, Globe } from "lucide-react"
69
// API调用现在通过Next.js API路由处理,不再需要直接导入
710
import { useFragmentStore } from "@/lib/stores/fragment-store"
11+
import { WebFragmentInput } from "./web-fragment-input"
812

913
export function FragmentInput() {
1014
const [text, setText] = useState("")
@@ -127,20 +131,48 @@ export function FragmentInput() {
127131
}
128132

129133
return (
130-
<div className="flex gap-2 ">
131-
<Textarea
132-
value={text}
133-
onChange={(e) => setText(e.target.value)}
134-
placeholder="输入新的知识碎片..."
135-
className="flex-1 min-h-[160px] resize-none"
136-
/>
137-
<Button
138-
onClick={handleAddFragment}
139-
disabled={isLoading}
140-
className="h-40 px-6"
141-
>
142-
{isLoading ? "添加中..." : "添加"}
143-
</Button>
134+
<div className="space-y-6">
135+
<Tabs defaultValue="text" className="w-full">
136+
<TabsList className="grid w-full grid-cols-2">
137+
<TabsTrigger value="text" className="flex items-center gap-2">
138+
<FileText className="w-4 h-4" />
139+
文本输入
140+
</TabsTrigger>
141+
<TabsTrigger value="web" className="flex items-center gap-2">
142+
<Globe className="w-4 h-4" />
143+
网页解析
144+
</TabsTrigger>
145+
</TabsList>
146+
147+
<TabsContent value="text" className="mt-6">
148+
<Card>
149+
<CardHeader>
150+
<CardTitle>手动输入知识碎片</CardTitle>
151+
</CardHeader>
152+
<CardContent>
153+
<div className="flex gap-2">
154+
<Textarea
155+
value={text}
156+
onChange={(e) => setText(e.target.value)}
157+
placeholder="输入新的知识碎片..."
158+
className="flex-1 min-h-[160px] resize-none"
159+
/>
160+
<Button
161+
onClick={handleAddFragment}
162+
disabled={isLoading}
163+
className="h-40 px-6"
164+
>
165+
{isLoading ? "添加中..." : "添加"}
166+
</Button>
167+
</div>
168+
</CardContent>
169+
</Card>
170+
</TabsContent>
171+
172+
<TabsContent value="web" className="mt-6">
173+
<WebFragmentInput />
174+
</TabsContent>
175+
</Tabs>
144176
</div>
145177
)
146178
}

0 commit comments

Comments
 (0)