Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0933ae7
feat(anyhunt/video-transcript): implement dual-mode transcript pipeline
dvlindev Feb 9, 2026
db8545f
Merge branch 'main' into video2text
dvlin-dev Feb 10, 2026
3734efe
fix(anyhunt/video-transcript): guard local workspace init
dvlindev Feb 10, 2026
7f1f48e
fix(anyhunt/video-transcript): harden platform allowlist
dvlindev Feb 10, 2026
a1e6b7a
fix(anyhunt/video-transcript): avoid overwriting terminal state
dvlindev Feb 10, 2026
2ca0231
fix(queue): isolate video transcript queues
dvlindev Feb 10, 2026
8296e60
Merge remote-tracking branch 'origin/video2text' into video2text
dvlindev Feb 10, 2026
5d4143e
fix(anyhunt/video-transcript): guard status transitions
dvlindev Feb 10, 2026
9246062
feat(anyhunt/video-transcript): add dedicated worker mode
dvlindev Feb 10, 2026
0efdedb
chore(anyhunt/video-transcript): align admin/console UI copy
dvlindev Feb 10, 2026
5b7dd50
docs(video-transcript): update deployment docs
dvlindev Feb 10, 2026
796afe2
fix(anyhunt/video-transcript): harden cancel race and budget eval args
dvlindev Feb 10, 2026
dc8bff0
docs(architecture): sync video transcript pipeline progress
dvlindev Feb 10, 2026
3d25c5c
Merge origin/main into video2text
dvlin-dev Mar 6, 2026
065541d
fix(anyhunt/video-transcript): finalize cloud preflight failures
dvlin-dev Mar 6, 2026
6ee2dd8
fix(anyhunt/console): align video transcript page ui imports
dvlin-dev Mar 6, 2026
2dc6653
fix(anyhunt/video-transcript): close queue and ownership races
dvlin-dev Mar 6, 2026
aa6aa70
fix(anyhunt/video-transcript): preserve retries and toggle audit state
dvlin-dev Mar 6, 2026
28d91c1
fix(anyhunt/video-transcript): rollback timeout budget reservations
dvlin-dev Mar 6, 2026
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
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ Anyhunt/
2. **实施**:聚焦单一问题,不盲改
3. **测试**:新功能必须编写单元测试,修复 bug 需补充回归测试
4. **校验(风险分级)**:按变更风险执行,避免低风险改动重复跑全量流水线:
- **L0(低风险)**:纯样式/文案/布局微调、无状态流与业务逻辑变更
- **L0(低风险)**:纯样式/文案/布局微调、无状态流与业务逻辑变更
可跳过全量 `lint/typecheck/test:unit`,按需做手工验证
- **L1(中风险)**:组件交互、状态管理、数据映射、非核心逻辑重构
- **L1(中风险)**:组件交互、状态管理、数据映射、非核心逻辑重构
至少运行受影响包的 `typecheck` 与 `test:unit`
- **L2(高风险)**:核心业务逻辑、跨包接口、后端模块、构建/基础设施改动
- **L2(高风险)**:核心业务逻辑、跨包接口、后端模块、构建/基础设施改动
必须运行以下命令并全部通过:

```bash
Expand Down
34 changes: 18 additions & 16 deletions apps/anyhunt/admin/www/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Anyhunt Dev 管理后台,用于系统监控与运营管理,需管理员权

## 最近更新

- Video Transcript 运维看板(2026-03-06):新增 `/video-transcripts` 页面,覆盖 overview/resources/tasks/config 与 local runtime switch;Queues 页面用户可见文案统一英文并复用 `formatRelativeTime`
- Build/Reasoning 类型链路收敛(2026-03-02):`model-form.ts` 调用 `resolveReasoningConfigFromThinkingLevel` 前统一将 `providerType` 归一为 `string | undefined`,消除 `string | null` 漂移;Dockerfile 改为复制完整 workspace 并统一执行 `pnpm build:packages`,根治容器内 `@moryflow/model-bank` 解析漂移导致的 TS2307。
- LLM Model 弹窗 reasoning 改造(2026-02-27):表单从 `effort` 选择切换为 `thinking level` 合同驱动(来自 `@moryflow/model-bank`),UI 展示等级只读参数摘要,提交时在单点 mapper 完成 `level -> reasoning(effort/maxTokens/includeThoughts)` 映射。
- 前端组件优化(Props 收敛专项):完成高 props 组件对象化改造(`digest-welcome` 三卡片、`digest-topics` 两列表、`queues/QueueJobsPanel`、`llm` 三弹窗、`users/GrantConfirmDialog`),统一为 `viewModel + actions`;多状态 UI 继续使用状态片段化 `renderContentByState + switch`;复扫结果 `Props >= 8` 组件数降为 0,校验 `typecheck + test:unit + lint + build` 通过
Expand Down Expand Up @@ -98,22 +99,23 @@ Anyhunt Dev 管理后台,用于系统监控与运营管理,需管理员权

## 功能列表

| 功能 | 路径 | 说明 |
| ----------------- | ----------------- | -------------------------- |
| `dashboard/` | `/` | 系统概览与统计 |
| `users/` | `/users` | 用户管理 |
| `subscriptions/` | `/subscriptions` | Subscription list |
| `orders/` | `/orders` | Order history |
| `jobs/` | `/jobs` | Crawl/batch job monitoring |
| `queues/` | `/queues` | BullMQ queue status |
| `browser/` | `/browser` | Browser pool instances |
| `logs/requests` | `/logs/requests` | Unified request logs |
| `logs/users` | `/logs/users` | User behavior from logs |
| `logs/ip` | `/logs/ip` | IP monitoring from logs |
| `digest-topics/` | `/digest/topics` | Digest Topics 精选管理 |
| `digest-reports/` | `/digest/reports` | Digest 举报管理 |
| `digest-welcome/` | `/digest/welcome` | Digest Welcome 配置与页面 |
| `llm/` | `/llm` | LLM Providers/Models 配置 |
| 功能 | 路径 | 说明 |
| -------------------- | -------------------- | --------------------------- |
| `dashboard/` | `/` | 系统概览与统计 |
| `users/` | `/users` | 用户管理 |
| `subscriptions/` | `/subscriptions` | Subscription list |
| `orders/` | `/orders` | Order history |
| `jobs/` | `/jobs` | Crawl/batch job monitoring |
| `queues/` | `/queues` | BullMQ queue status |
| `video-transcripts/` | `/video-transcripts` | Video transcript operations |
| `browser/` | `/browser` | Browser pool instances |
| `logs/requests` | `/logs/requests` | Unified request logs |
| `logs/users` | `/logs/users` | User behavior from logs |
| `logs/ip` | `/logs/ip` | IP monitoring from logs |
| `digest-topics/` | `/digest/topics` | Digest Topics 精选管理 |
| `digest-reports/` | `/digest/reports` | Digest 举报管理 |
| `digest-welcome/` | `/digest/welcome` | Digest Welcome 配置与页面 |
| `llm/` | `/llm` | LLM Providers/Models 配置 |

## Feature Module Structure

Expand Down
1 change: 0 additions & 1 deletion apps/anyhunt/admin/www/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*
* [PROTOCOL]: 本文件变更时,需同步更新所属目录 CLAUDE.md
*/

import { AppProviders } from '@/app/AppProviders';
import { AppRouter } from '@/app/AppRouter';

Expand Down
13 changes: 13 additions & 0 deletions apps/anyhunt/admin/www/src/app/admin-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TriangleAlert,
Globe,
Brain,
Clapperboard,
CreditCard,
LayoutDashboard,
Layers,
Expand Down Expand Up @@ -74,6 +75,7 @@ const SubscriptionsPage = lazy(() => import('@/pages/SubscriptionsPage'));
const JobsPage = lazy(() => import('@/pages/JobsPage'));
const JobDetailPage = lazy(() => import('@/pages/JobDetailPage'));
const QueuesPage = lazy(() => import('@/pages/QueuesPage'));
const VideoTranscriptsPage = lazy(() => import('@/pages/VideoTranscriptsPage'));
const ErrorsPage = lazy(() => import('@/pages/ErrorsPage'));
const LogsRequestsPage = lazy(() => import('@/pages/logs/LogsRequestsPage'));
const LogsUsersPage = lazy(() => import('@/pages/logs/LogsUsersPage'));
Expand Down Expand Up @@ -189,6 +191,17 @@ export const ADMIN_PROTECTED_ROUTES: AdminProtectedRoute[] = [
icon: Layers,
},
},
{
id: 'video-transcripts',
path: 'video-transcripts',
component: VideoTranscriptsPage,
nav: {
groupId: 'operations',
path: '/video-transcripts',
label: 'Video Transcripts',
icon: Clapperboard,
},
},
{
id: 'browser',
path: 'browser',
Expand Down
30 changes: 16 additions & 14 deletions apps/anyhunt/admin/www/src/features/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

## 最近更新

- Video Transcript Feature(2026-03-06):新增 `video-transcripts/` 模块,对接 `/api/v1/admin/video-transcripts/*` 的 overview/resources/tasks/config,并提供 local runtime switch mutation
- LLM Feature 表单映射修复(2026-02-27):`forms/model-form.ts` 的已存储 reasoning 反向映射改为 `normalizeReasoningEffort(level.value)`,修复 `max` 等级与 `xhigh` effort 的语义匹配不一致。
- LLM Feature 合同化(2026-02-27):`forms/model-form.ts` 改为 `thinking level` 驱动,删除 `KNOWN_REASONING_EFFORTS` 作为主事实源;`toLlmReasoningConfig` 集中完成 `level -> reasoning` 映射并保留 `rawConfig` 透传。
- Props 收敛专项:`digest-topics`(`AllTopicsListContent/FeaturedTopicsListContent`)、`queues`(`QueueJobsPanel`)、`users`(`GrantConfirmDialog`)完成 `viewModel + actions` 对象化收敛;调用页同步去胶水 props,保持多状态片段化 `switch` 分发
Expand Down Expand Up @@ -38,20 +39,21 @@ feature-name/

## 功能清单

| 功能 | 说明 | API 入口 |
| ----------------- | --------------------------- | ------------------------------ |
| `dashboard/` | 系统概览 | `/api/v1/admin/dashboard` |
| `users/` | 用户管理(含 Credits 充值) | `/api/v1/admin/users` |
| `subscriptions/` | 订阅管理 | `/api/v1/admin/subscriptions` |
| `orders/` | 订单管理 | `/api/v1/admin/orders` |
| `jobs/` | 任务监控 | `/api/v1/admin/jobs` |
| `queues/` | 队列监控 | `/api/v1/admin/queues` |
| `browser/` | 浏览器池状态 | `/api/v1/admin/browser` |
| `logs/` | 请求日志与行为分析 | `/api/v1/admin/logs/*` |
| `llm/` | LLM Providers/Models 配置 | `/api/v1/admin/llm/*` |
| `digest-topics/` | Digest 话题管理 | `/api/v1/admin/digest/topics` |
| `digest-reports/` | Digest 举报管理 | `/api/v1/admin/digest/reports` |
| `digest-welcome/` | Welcome 配置 | `/api/v1/admin/digest/welcome` |
| 功能 | 说明 | API 入口 |
| -------------------- | --------------------------- | ----------------------------------- |
| `dashboard/` | 系统概览 | `/api/v1/admin/dashboard` |
| `users/` | 用户管理(含 Credits 充值) | `/api/v1/admin/users` |
| `subscriptions/` | 订阅管理 | `/api/v1/admin/subscriptions` |
| `orders/` | 订单管理 | `/api/v1/admin/orders` |
| `jobs/` | 任务监控 | `/api/v1/admin/jobs` |
| `queues/` | 队列监控 | `/api/v1/admin/queues` |
| `video-transcripts/` | 视频转写可观测 | `/api/v1/admin/video-transcripts/*` |
| `browser/` | 浏览器池状态 | `/api/v1/admin/browser` |
| `logs/` | 请求日志与行为分析 | `/api/v1/admin/logs/*` |
| `llm/` | LLM Providers/Models 配置 | `/api/v1/admin/llm/*` |
| `digest-topics/` | Digest 话题管理 | `/api/v1/admin/digest/topics` |
| `digest-reports/` | Digest 举报管理 | `/api/v1/admin/digest/reports` |
| `digest-welcome/` | Welcome 配置 | `/api/v1/admin/digest/welcome` |

## 轮询刷新示例

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export function QueueActionConfirmDialog({
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认操作</AlertDialogTitle>
<AlertDialogTitle>Confirm action</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>确认</AlertDialogAction>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function QueueCard({
<span>{QUEUE_LABELS[stats.name]}</span>
{isPaused ? (
<Badge variant="outline" className="text-yellow-600">
已暂停
Paused
</Badge>
) : null}
</CardTitle>
Expand All @@ -45,15 +45,15 @@ function QueueCard({
<div className="grid grid-cols-3 gap-2 text-center">
<div>
<p className="text-2xl font-bold text-yellow-600">{stats.waiting}</p>
<p className="text-xs text-muted-foreground">等待</p>
<p className="text-xs text-muted-foreground">Waiting</p>
</div>
<div>
<p className="text-2xl font-bold text-blue-600">{stats.active}</p>
<p className="text-xs text-muted-foreground">处理中</p>
<p className="text-xs text-muted-foreground">Active</p>
</div>
<div>
<p className="text-2xl font-bold text-red-600">{stats.failed}</p>
<p className="text-xs text-muted-foreground">失败</p>
<p className="text-xs text-muted-foreground">Failed</p>
</div>
</div>
</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ export function QueueJobsPanel({ viewModel, actions }: QueueJobsPanelProps) {
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>
{QUEUE_LABELS[selectedQueue]} 队列
{QUEUE_LABELS[selectedQueue]} Queue
{isPaused ? (
<Badge variant="outline" className="ml-2 text-yellow-600">
已暂停
Paused
</Badge>
) : null}
</CardTitle>
Expand All @@ -82,12 +82,12 @@ export function QueueJobsPanel({ viewModel, actions }: QueueJobsPanelProps) {
{isPaused ? (
<>
<Play className="mr-2 h-4 w-4" />
恢复
Resume
</>
) : (
<>
<Pause className="mr-2 h-4 w-4" />
暂停
Pause
</>
)}
</Button>
Expand All @@ -98,11 +98,11 @@ export function QueueJobsPanel({ viewModel, actions }: QueueJobsPanelProps) {
disabled={isRetrying || (selectedStats?.failed ?? 0) === 0}
>
<RotateCcw className="mr-2 h-4 w-4" />
重试全部失败
Retry all failed
</Button>
<Button variant="outline" size="sm" onClick={onCleanCompleted} disabled={isCleaning}>
<Delete className="mr-2 h-4 w-4" />
清理已完成
Clean completed
</Button>
</div>
</div>
Expand Down
20 changes: 11 additions & 9 deletions apps/anyhunt/admin/www/src/features/queues/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ export const QUEUE_LABELS: Record<QueueName, string> = {
scrape: 'Scrape',
crawl: 'Crawl',
'batch-scrape': 'Batch Scrape',
VIDEO_TRANSCRIPT_LOCAL_QUEUE: 'Video Transcript (Local)',
VIDEO_TRANSCRIPT_CLOUD_FALLBACK_QUEUE: 'Video Transcript (Cloud Fallback)',
};

export const QUEUE_STATUS_TABS: Array<{ value: QueueJobStatus; label: string }> = [
{ value: 'waiting', label: '等待中' },
{ value: 'active', label: '处理中' },
{ value: 'completed', label: '已完成' },
{ value: 'failed', label: '失败' },
{ value: 'delayed', label: '延迟' },
{ value: 'waiting', label: 'Waiting' },
{ value: 'active', label: 'Active' },
{ value: 'completed', label: 'Completed' },
{ value: 'failed', label: 'Failed' },
{ value: 'delayed', label: 'Delayed' },
];

export type QueueConfirmAction = 'retry' | 'clean-completed' | 'clean-failed' | 'cleanup-stale';
Expand All @@ -31,13 +33,13 @@ export function getQueueConfirmDescription(

switch (action) {
case 'retry':
return `确定要重试 ${queueLabel} 队列中所有失败的任务吗?`;
return `Retry all failed jobs in "${queueLabel}"?`;
case 'clean-completed':
return `确定要清理 ${queueLabel} 队列中所有已完成的任务吗?`;
return `Clean all completed jobs in "${queueLabel}"?`;
case 'clean-failed':
return `确定要清理 ${queueLabel} 队列中所有失败的任务吗?`;
return `Clean all failed jobs in "${queueLabel}"?`;
case 'cleanup-stale':
return '确定要清理所有卡住超过 30 分钟的任务吗?这些任务将被标记为失败。';
return 'Cleanup all jobs stuck for more than 30 minutes? Those jobs will be marked as failed.';
default:
return '';
}
Expand Down
2 changes: 1 addition & 1 deletion apps/anyhunt/admin/www/src/features/queues/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type { Pagination } from '@/lib/types';
import type { Pagination } from '@/lib/types';

/** 队列名称 */
export type QueueName = 'screenshot' | 'scrape' | 'crawl' | 'batch-scrape';
export type QueueName = string;

/** 队列任务状态 */
export type QueueJobStatus = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed';
Expand Down
53 changes: 53 additions & 0 deletions apps/anyhunt/admin/www/src/features/video-transcripts/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* [PROVIDES]: Admin Video Transcript API 方法
* [DEPENDS]: apiClient, ADMIN_API
* [POS]: Admin Video Transcript API 访问层
*
* [PROTOCOL]: 本文件变更时,需同步更新所属目录 CLAUDE.md
*/

import { apiClient } from '@/lib/api-client';
import { ADMIN_API } from '@/lib/api-paths';
import type {
UpdateVideoTranscriptRuntimeConfigInput,
UpdateVideoTranscriptRuntimeConfigResponse,
VideoTranscriptRuntimeConfig,
VideoTranscriptOverview,
VideoTranscriptResources,
VideoTranscriptTaskListResponse,
} from './types';

export async function getVideoTranscriptOverview(): Promise<VideoTranscriptOverview> {
return apiClient.get<VideoTranscriptOverview>(`${ADMIN_API.VIDEO_TRANSCRIPTS}/overview`);
}

export async function getVideoTranscriptResources(): Promise<VideoTranscriptResources> {
return apiClient.get<VideoTranscriptResources>(`${ADMIN_API.VIDEO_TRANSCRIPTS}/resources`);
}

export async function getVideoTranscriptRuntimeConfig(): Promise<VideoTranscriptRuntimeConfig> {
return apiClient.get<VideoTranscriptRuntimeConfig>(`${ADMIN_API.VIDEO_TRANSCRIPTS}/config`);
}

export async function updateVideoTranscriptRuntimeConfig(
input: UpdateVideoTranscriptRuntimeConfigInput
): Promise<UpdateVideoTranscriptRuntimeConfigResponse> {
return apiClient.put<UpdateVideoTranscriptRuntimeConfigResponse>(
`${ADMIN_API.VIDEO_TRANSCRIPTS}/config/local-enabled`,
input
);
}

export async function getVideoTranscriptTasks(
page = 1,
limit = 20
): Promise<VideoTranscriptTaskListResponse> {
const query = new URLSearchParams({
page: String(page),
limit: String(limit),
}).toString();

return apiClient.get<VideoTranscriptTaskListResponse>(
`${ADMIN_API.VIDEO_TRANSCRIPTS}/tasks?${query}`
);
}
Loading
Loading