feat: Add source code viewer with Gitea integration

- Add /source page with file explorer and code viewer
- Connect to Gitea API for repository browsing
- Implement MCP tools: get_repo_info, list_repo_files, read_repo_file, search_repo_code
- Default to nucleo000/agent-ui repository
- Support branch switching and file search
This commit is contained in:
2026-02-13 06:50:58 -06:00
parent 97ef49aea4
commit 2a2100bbb2
7 changed files with 1401 additions and 2 deletions

View File

@@ -861,6 +861,142 @@ Bun.serve({
}, { headers: corsHeaders })
}
// =====================
// API de Gitea (Source Code Viewer)
// =====================
// POST /api/gitea/repo - Connect and get repo info
if (url.pathname === '/api/gitea/repo' && req.method === 'POST') {
const body = await req.json()
const { giteaUrl, username, password, owner, repo } = body
if (!giteaUrl || !username || !password || !owner || !repo) {
return Response.json({ error: 'Missing required fields' }, { status: 400, headers: corsHeaders })
}
try {
const auth = Buffer.from(`${username}:${password}`).toString('base64')
// Get repo info
const repoRes = await fetch(`${giteaUrl}/api/v1/repos/${owner}/${repo}`, {
headers: { 'Authorization': `Basic ${auth}` }
})
if (!repoRes.ok) {
if (repoRes.status === 401) {
return Response.json({ error: 'Invalid credentials' }, { status: 401, headers: corsHeaders })
}
if (repoRes.status === 404) {
return Response.json({ error: 'Repository not found' }, { status: 404, headers: corsHeaders })
}
throw new Error('Failed to connect to Gitea')
}
const repoData = await repoRes.json()
// Get branches
const branchesRes = await fetch(`${giteaUrl}/api/v1/repos/${owner}/${repo}/branches`, {
headers: { 'Authorization': `Basic ${auth}` }
})
let branches = ['main']
if (branchesRes.ok) {
const branchesData = await branchesRes.json()
branches = branchesData.map((b: any) => b.name)
}
return Response.json({
repo: {
name: repoData.name,
description: repoData.description,
default_branch: repoData.default_branch,
stars_count: repoData.stars_count,
forks_count: repoData.forks_count,
owner: { login: repoData.owner?.login || owner }
},
branches
}, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 500, headers: corsHeaders })
}
}
// POST /api/gitea/tree - Get file tree
if (url.pathname === '/api/gitea/tree' && req.method === 'POST') {
const body = await req.json()
const { giteaUrl, username, password, owner, repo, branch, path } = body
try {
const auth = Buffer.from(`${username}:${password}`).toString('base64')
const apiPath = path ? `${giteaUrl}/api/v1/repos/${owner}/${repo}/contents/${path}?ref=${branch}`
: `${giteaUrl}/api/v1/repos/${owner}/${repo}/contents?ref=${branch}`
const res = await fetch(apiPath, {
headers: { 'Authorization': `Basic ${auth}` }
})
if (!res.ok) {
throw new Error('Failed to load tree')
}
const data = await res.json()
const items = Array.isArray(data) ? data : [data]
const tree = items
.map((item: any) => ({
name: item.name,
path: item.path,
type: item.type === 'dir' ? 'dir' : 'file',
children: item.type === 'dir' ? [] : undefined
}))
.sort((a: any, b: any) => {
// Folders first, then files
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1
return a.name.localeCompare(b.name)
})
return Response.json({ tree }, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 500, headers: corsHeaders })
}
}
// POST /api/gitea/file - Get file content
if (url.pathname === '/api/gitea/file' && req.method === 'POST') {
const body = await req.json()
const { giteaUrl, username, password, owner, repo, branch, path } = body
try {
const auth = Buffer.from(`${username}:${password}`).toString('base64')
const res = await fetch(
`${giteaUrl}/api/v1/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
{ headers: { 'Authorization': `Basic ${auth}` } }
)
if (!res.ok) {
throw new Error('Failed to load file')
}
const data = await res.json()
// Decode base64 content
let content = ''
if (data.content) {
content = Buffer.from(data.content, 'base64').toString('utf-8')
}
return Response.json({
content,
encoding: data.encoding,
size: data.size,
sha: data.sha
}, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 500, headers: corsHeaders })
}
}
// =====================
// API de Database Explorer
// =====================