auto-save 2026-04-13 18:59 (+1)

This commit is contained in:
2026-04-13 19:00:17 +08:00
parent c35c1852af
commit 4bdc14f96c
88 changed files with 11213 additions and 0 deletions

185
web/lib/mock-data.ts Normal file
View File

@@ -0,0 +1,185 @@
export type MeetingStatus =
| 'pending'
| 'uploading'
| 'transcribing'
| 'summarizing'
| 'done'
| 'failed'
export type Meeting = {
id: string
title: string
date: string // ISO
duration: number // seconds
participants?: string[]
status: MeetingStatus
progress?: number // 0-100
chunksDone?: number
chunksTotal?: number
summary?: {
keyPoints: string[]
todos: { text: string; owner?: string; due?: string }[]
decisions: string[]
keywords: string[]
preview: string
}
transcript?: Array<{
time: number // seconds
speaker: string
text: string
}>
}
export const mockMeetings: Meeting[] = [
{
id: 'm1',
title: '与客户对齐定价',
date: '2026-04-13T14:30:00',
duration: 4980,
participants: ['张三', '李四', '客户王总'],
status: 'transcribing',
progress: 60,
chunksDone: 3,
chunksTotal: 5,
},
{
id: 'm2',
title: '团队周会',
date: '2026-04-13T10:00:00',
duration: 2700,
participants: ['张三', '李四', '王五'],
status: 'done',
summary: {
keyPoints: [
'A 项目完成 80%,进入最后测试阶段',
'B 项目本周进入功能测试',
'下周聚焦 A 项目上线准备,需对齐运营',
],
todos: [
{ text: '整理 A 项目上线 checklist', owner: '张三', due: '周五前' },
{ text: '协调运营团队对接会', owner: '李四', due: '明天' },
{ text: 'B 项目性能压测报告', owner: '王五' },
],
decisions: [
'A 项目上线时间定为下周三',
'B 项目延后一周,优先保质量',
],
keywords: ['周会', '进度', '上线', 'A项目', 'B项目'],
preview:
'本周关键进展A 项目完成 80%进入最后测试阶段B 项目本周进入功能测试。下周聚焦 A 项目上线准备,需要协调运营团队对接...',
},
},
{
id: 'm3',
title: '产品 Review',
date: '2026-04-12T16:00:00',
duration: 3120,
participants: ['产品经理', '设计师', '工程团队'],
status: 'done',
summary: {
keyPoints: [
'确定 Q2 产品路线图三个重点方向',
'移动端体验优化优先级最高',
'AI 功能纳入 Q3 规划',
],
todos: [
{ text: '出 Q2 PRD 初稿', owner: '产品经理', due: '下周一' },
{ text: '设计 Mobile 重构方案', owner: '设计师' },
],
decisions: ['Q2 聚焦移动端', 'AI 功能推迟到 Q3'],
keywords: ['产品', 'Q2', '路线图', '移动端', 'AI'],
preview:
'讨论 Q2 产品路线图确定三个重点1) 移动端体验优化作为最高优先级2) 后台管理 UI 重构3) AI 辅助功能纳入 Q3 规划...',
},
},
{
id: 'm4',
title: '架构评审 · API 网关方案',
date: '2026-04-11T14:00:00',
duration: 5400,
participants: ['技术负责人', '后端团队'],
status: 'done',
summary: {
keyPoints: [
'选型确定 Kong + 自研插件方案',
'Rate limiting 复用 Redis 集群',
'灰度发布走 Canary',
],
todos: [
{ text: '写 Kong POC', owner: '小明', due: '下周五' },
{ text: '梳理现有 API 清单', owner: '小红' },
],
decisions: ['不走 EnvoyKong 生态更成熟', '灰度走 Canary 5%/25%/50%/100%'],
keywords: ['架构', 'API网关', 'Kong', '灰度', 'Redis'],
preview:
'今天主要对齐 API 网关的选型方向,在 Kong 和 Envoy 之间做了对比。最终决定 Kong + 自研插件,主要考虑到 Kong 的生态更成熟,团队学习成本更低...',
},
},
{
id: 'm5',
title: '用户访谈 · A 类客户',
date: '2026-04-10T15:30:00',
duration: 3600,
status: 'failed',
},
]
export function getMeeting(id: string): Meeting | undefined {
return mockMeetings.find((m) => m.id === id)
}
export function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = seconds % 60
if (h > 0) return `${h}h ${m}min`
return `${m}min`
}
export function formatTime(seconds: number): string {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor(seconds % 60)
if (h > 0) {
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
export function groupByDate(meetings: Meeting[]): Record<string, Meeting[]> {
const groups: Record<string, Meeting[]> = {}
for (const m of meetings) {
const day = m.date.slice(0, 10)
if (!groups[day]) groups[day] = []
groups[day].push(m)
}
return groups
}
export function dateLabel(day: string): string {
const today = new Date()
const todayStr = today.toISOString().slice(0, 10)
const yesterday = new Date(today)
yesterday.setDate(today.getDate() - 1)
const yesterdayStr = yesterday.toISOString().slice(0, 10)
if (day === todayStr) return '今天 · ' + day
if (day === yesterdayStr) return '昨天 · ' + day
return day
}
export const mockTranscript = [
{ time: 0, speaker: '张三', text: '大家好,今天主要是对齐一下 Q2 的定价方案pipeline 基本已经 ready 了。' },
{ time: 15, speaker: '李四', text: 'OK, 我这边准备了三个档位的方案,分别是 Basic / Pro / Ultra对应不同的客户规模。' },
{ time: 35, speaker: '客户王总', text: '我们比较关心 Pro 档的 SLA 具体怎么定义,还有超出 quota 之后的处理方式。' },
{ time: 58, speaker: '李四', text: 'Pro 档 SLA 我们承诺 99.9% 可用性,超出 quota 按标准价格 0.01 per request 计费。' },
{ time: 90, speaker: '张三', text: '关于 SLA我建议我们再明确一下 downtime 的定义,尤其是计划内维护是否算。' },
{ time: 110, speaker: '客户王总', text: '同意,计划内维护应该提前通知,并且不计入 SLA。' },
{ time: 130, speaker: '李四', text: '好的,这块我回去更新一下合同模板,会议纪要里也会明确写出来。' },
{ time: 155, speaker: '张三', text: '另外 Pro 档的折扣,我们能给到什么力度?客户这边期望是 15%。' },
{ time: 180, speaker: '客户王总', text: '15% 是我们的底线,毕竟首年采购量会比较大。' },
{ time: 205, speaker: '李四', text: '10% 是我们目前政策能给到的上限15% 需要走特批流程。' },
{ time: 230, speaker: '张三', text: '那就走特批,客户关系比单次毛利重要。我今天去找 CEO 签字。' },
{ time: 260, speaker: '客户王总', text: '感谢。那就这样,合同草稿我们希望周五之前能收到。' },
{ time: 280, speaker: '李四', text: '没问题,周五前一定出。' },
]

6
web/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}