Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ yarn-error.log*

# env files (can opt-in for committing if needed)
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
Expand Down
113 changes: 113 additions & 0 deletions app/api/jina/extract/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Jina AI 网页内容提取 API 路由
*/

import { NextRequest, NextResponse } from 'next/server'
import { jinaService } from '../../../../lib/jina-service'
import { WebContentExtractRequest } from '../../../../lib/jina-service'

export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { url, userPrompt, extractMode = 'full', language = 'zh' } = body

// 验证必需参数
if (!url) {
return NextResponse.json(
{ success: false, error: '缺少必需参数: url' },
{ status: 400 }
)
}

// 验证URL格式
try {
new URL(url)
} catch {
return NextResponse.json(
{ success: false, error: '无效的URL格式' },
{ status: 400 }
)
}

// 构建请求参数
const extractRequest: WebContentExtractRequest = {
url,
userPrompt,
extractMode,
language
}

// 调用Jina服务提取内容
const result = await jinaService.extractWebContent(extractRequest)

if (!result) {
return NextResponse.json(
{ success: false, error: '网页内容提取失败' },
{ status: 500 }
)
}

return NextResponse.json({
success: true,
data: result
})

} catch (error) {
console.error('Jina extract API error:', error)
return NextResponse.json(
{ success: false, error: '服务器内部错误' },
{ status: 500 }
)
}
}

export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const action = searchParams.get('action')

if (action === 'test') {
// 测试Jina服务连接
const testUrl = 'https://example.com'
const testResult = await jinaService.parseWebPage(testUrl)

return NextResponse.json({
success: true,
data: {
service: 'Jina AI',
status: testResult.success ? 'connected' : 'error',
error: testResult.error
}
})
}

if (action === 'config') {
// 返回服务配置信息
return NextResponse.json({
success: true,
data: {
readerApiEnabled: true,
deepSearchEnabled: !!process.env.JINA_API_KEY,
supportedModes: ['full', 'summary', 'custom'],
maxContentLength: 8000,
rateLimits: {
reader: '20 requests/minute (free), 500 requests/minute (paid)',
deepSearch: '50 requests/minute'
}
}
})
}

return NextResponse.json(
{ success: false, error: '无效的操作' },
{ status: 400 }
)

} catch (error) {
console.error('Jina config API error:', error)
return NextResponse.json(
{ success: false, error: '获取配置失败' },
{ status: 500 }
)
}
}
1 change: 1 addition & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function RootLayout({
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
suppressHydrationWarning={true}
>
<StoreInitializer>
<SidebarProvider>
Expand Down
60 changes: 46 additions & 14 deletions components/fragment-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import { useState } from "react"
import { Textarea } from "@/components/ui/textarea"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { FileText, Globe } from "lucide-react"
// API调用现在通过Next.js API路由处理,不再需要直接导入
import { useFragmentStore } from "@/lib/stores/fragment-store"
import { WebFragmentInput } from "./web-fragment-input"

export function FragmentInput() {
const [text, setText] = useState("")
Expand Down Expand Up @@ -127,20 +131,48 @@ export function FragmentInput() {
}

return (
<div className="flex gap-2 ">
<Textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入新的知识碎片..."
className="flex-1 min-h-[160px] resize-none"
/>
<Button
onClick={handleAddFragment}
disabled={isLoading}
className="h-40 px-6"
>
{isLoading ? "添加中..." : "添加"}
</Button>
<div className="space-y-6">
<Tabs defaultValue="text" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="text" className="flex items-center gap-2">
<FileText className="w-4 h-4" />
文本输入
</TabsTrigger>
<TabsTrigger value="web" className="flex items-center gap-2">
<Globe className="w-4 h-4" />
网页解析
</TabsTrigger>
</TabsList>

<TabsContent value="text" className="mt-6">
<Card>
<CardHeader>
<CardTitle>手动输入知识碎片</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-2">
<Textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入新的知识碎片..."
className="flex-1 min-h-[160px] resize-none"
/>
<Button
onClick={handleAddFragment}
disabled={isLoading}
className="h-40 px-6"
>
{isLoading ? "添加中..." : "添加"}
</Button>
</div>
</CardContent>
</Card>
</TabsContent>

<TabsContent value="web" className="mt-6">
<WebFragmentInput />
</TabsContent>
</Tabs>
</div>
)
}
Loading