Hair Keeper v1.0.0:一个高度集成、深度定制、约定优于配置的全栈Web应用模板,旨在保持灵活性的同时提供一套基于成熟架构的开发底座,自带身份认证、权限控制、丰富前端组件、文件上传、后台任务、智能体开发等丰富功能,提供AI开发辅助,免于纠结功能如何实现,可快速上手专注于业务逻辑

This commit is contained in:
2025-11-13 15:24:54 +08:00
commit 42be39b343
249 changed files with 38843 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
'use client'
import React from 'react'
import { trpc } from '@/lib/trpc'
import { Button } from '@/components/ui/button'
import { FileSearch } from 'lucide-react'
import { toast } from 'sonner'
import { TaskDialog, BaseTaskProgress } from '@/components/common/task-dialog'
import type { AnalyzePackagesProgress } from '@/server/queues'
/**
* 扩展的分析进度类型
*/
interface AnalyzeProgress extends BaseTaskProgress, AnalyzePackagesProgress {}
interface PackageAnalyzeDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
jobId: string | null
onAnalyzeCompleted: () => void
}
interface PackageAnalyzeTriggerProps {
onStartAnalyze: () => void
isStarting: boolean
}
/**
* 依赖包分析触发器按钮
*/
export function PackageAnalyzeTrigger({
onStartAnalyze,
isStarting
}: PackageAnalyzeTriggerProps) {
return (
<Button
variant="default"
onClick={onStartAnalyze}
disabled={isStarting}
>
<FileSearch className="mr-2 h-4 w-4" />
{isStarting ? '启动中...' : '依赖包分析'}
</Button>
)
}
/**
* 依赖包分析进度对话框
*/
export function PackageAnalyzeDialog({
open,
onOpenChange,
jobId,
onAnalyzeCompleted
}: PackageAnalyzeDialogProps) {
// 停止分析任务 mutation
const cancelMutation = trpc.devArch!.cancelAnalyzePackagesJob.useMutation({
onSuccess: () => {
toast.success('已发送停止请求')
},
onError: (error) => {
toast.error(error.message || '停止任务失败')
},
})
// 停止任务
const handleCancelTask = async (taskJobId: string) => {
await cancelMutation.mutateAsync({ jobId: taskJobId })
}
// 自定义状态消息渲染
const renderStatusMessage = (progress: AnalyzeProgress) => {
if (progress.state === 'waiting') {
return '任务等待中...'
} else if (progress.state === 'active') {
if (progress.currentPackage) {
return `正在分析: ${progress.currentPackage}`
}
return '正在分析依赖包...'
} else if (progress.state === 'completed') {
const successCount = (progress.analyzedPackages || 0) - (progress.failedPackages || 0)
const failedCount = progress.failedPackages || 0
const skippedCount = progress.skippedPackages || 0
const parts = [`成功 ${successCount}`]
if (failedCount > 0) {
parts.push(`失败 ${failedCount}`)
}
if (skippedCount > 0) {
parts.push(`跳过 ${skippedCount}`)
}
parts.push(`${progress.totalPackages || 0} 个依赖包`)
return `分析完成!${parts.join('')}`
} else if (progress.state === 'failed') {
return progress.error || '分析失败'
}
return ''
}
// 自定义详细信息渲染
const renderDetails = (progress: AnalyzeProgress) => {
if (progress.totalPackages === undefined && progress.analyzedPackages === undefined) {
return null
}
const successCount = (progress.analyzedPackages || 0) - (progress.failedPackages || 0)
return (
<div className="space-y-4">
{/* 进度统计 */}
<div className="grid grid-cols-2 gap-4 text-sm">
{progress.totalPackages !== undefined && (
<div>
<span className="text-muted-foreground"></span>
<span className="ml-1 font-medium">{progress.totalPackages}</span>
</div>
)}
{progress.analyzedPackages !== undefined && (
<div>
<span className="text-muted-foreground"></span>
<span className="ml-1 font-medium">{progress.analyzedPackages}</span>
</div>
)}
{successCount > 0 && (
<div>
<span className="text-muted-foreground"></span>
<span className="ml-1 font-medium text-green-600">{successCount}</span>
</div>
)}
{progress.failedPackages !== undefined && progress.failedPackages > 0 && (
<div>
<span className="text-muted-foreground"></span>
<span className="ml-1 font-medium text-red-600">{progress.failedPackages}</span>
</div>
)}
{progress.skippedPackages !== undefined && progress.skippedPackages > 0 && (
<div>
<span className="text-muted-foreground"></span>
<span className="ml-1 font-medium text-blue-600">{progress.skippedPackages}</span>
</div>
)}
</div>
{/* 当前处理的依赖包 */}
{progress.currentPackage && progress.state === 'active' && (
<div className="rounded-md bg-muted p-3 text-sm">
<div className="text-muted-foreground mb-1"></div>
<div className="font-mono text-xs break-all">{progress.currentPackage}</div>
</div>
)}
{/* 最近的错误信息 */}
{progress.recentErrors && progress.recentErrors.length > 0 && (
<div className="rounded-md border border-red-200 bg-red-50 p-3 text-sm">
<div className="text-red-800 font-medium mb-2"> (10)</div>
<div className="space-y-2 max-h-40 overflow-y-auto">
{progress.recentErrors.map((err, index) => (
<div key={index} className="text-xs">
<div className="font-mono text-red-700 break-all">{err.packageName}</div>
<div className="text-red-600 mt-1">{err.error}</div>
{index < progress.recentErrors!.length - 1 && (
<div className="border-t border-red-200 mt-2" />
)}
</div>
))}
</div>
</div>
)}
</div>
)
}
return (
<TaskDialog<AnalyzeProgress>
open={open}
onOpenChange={onOpenChange}
useSubscription={trpc.jobs.subscribeAnalyzePackagesProgress.useSubscription}
jobId={jobId}
title="依赖包分析进度"
description="正在使用AI分析项目依赖包请稍候..."
onCancelTask={handleCancelTask}
onTaskCompleted={onAnalyzeCompleted}
isCancelling={cancelMutation.isPending}
renderStatusMessage={renderStatusMessage}
renderDetails={renderDetails}
/>
)
}