Initial commit
This commit is contained in:
250
lib/background-music.ts
Normal file
250
lib/background-music.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
// 背景音樂管理系統 - 修復重新播放問題
|
||||
class BackgroundMusicManager {
|
||||
private audio: HTMLAudioElement | null = null
|
||||
private isPlaying = false
|
||||
private enabled = false
|
||||
private volume = 0.3
|
||||
private fadeInterval: NodeJS.Timeout | null = null
|
||||
|
||||
constructor() {
|
||||
this.initAudio()
|
||||
this.loadState()
|
||||
}
|
||||
|
||||
private loadState() {
|
||||
try {
|
||||
const savedState = localStorage.getItem("backgroundMusicState")
|
||||
if (savedState) {
|
||||
const state = JSON.parse(savedState)
|
||||
this.volume = state.volume || 0.3
|
||||
this.enabled = state.enabled || false
|
||||
// 不要自動恢復播放狀態,讓用戶手動控制
|
||||
this.isPlaying = false
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略載入錯誤
|
||||
}
|
||||
}
|
||||
|
||||
private saveState() {
|
||||
try {
|
||||
const state = {
|
||||
volume: this.volume,
|
||||
enabled: this.enabled,
|
||||
isPlaying: this.isPlaying,
|
||||
}
|
||||
localStorage.setItem("backgroundMusicState", JSON.stringify(state))
|
||||
} catch (error) {
|
||||
// 忽略保存錯誤
|
||||
}
|
||||
}
|
||||
|
||||
private initAudio() {
|
||||
try {
|
||||
this.audio = new Audio("https://hebbkx1anhila5yf.public.blob.vercel-storage.com/just-relax-11157-iAgp15dV2YGybAezUJFtKmKZPbteXd.mp3")
|
||||
this.audio.loop = true
|
||||
this.audio.volume = this.volume
|
||||
this.audio.preload = "metadata"
|
||||
|
||||
// 明確禁用自動播放相關屬性
|
||||
this.audio.autoplay = false
|
||||
this.audio.muted = false
|
||||
|
||||
// 靜默處理載入完成
|
||||
this.audio.addEventListener("canplaythrough", () => {
|
||||
// 音樂載入完成
|
||||
})
|
||||
|
||||
// 靜默錯誤處理
|
||||
this.audio.addEventListener("error", (e) => {
|
||||
// 重新初始化音頻對象
|
||||
this.reinitAudio()
|
||||
})
|
||||
|
||||
// 播放結束處理
|
||||
this.audio.addEventListener("ended", () => {
|
||||
if (this.enabled && this.isPlaying) {
|
||||
// 重新播放
|
||||
this.audio?.play().catch(() => {
|
||||
// 如果播放失敗,重新初始化
|
||||
this.reinitAudio()
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
// 靜默處理初始化錯誤
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化音頻對象
|
||||
private reinitAudio() {
|
||||
try {
|
||||
if (this.audio) {
|
||||
this.audio.pause()
|
||||
this.audio.src = ""
|
||||
this.audio = null
|
||||
}
|
||||
|
||||
// 重新創建音頻對象
|
||||
setTimeout(() => {
|
||||
this.initAudio()
|
||||
}, 100)
|
||||
} catch (error) {
|
||||
// 靜默處理錯誤
|
||||
}
|
||||
}
|
||||
|
||||
// 淡入效果
|
||||
private fadeIn(duration = 2000) {
|
||||
if (!this.audio) return
|
||||
|
||||
this.audio.volume = 0
|
||||
const targetVolume = this.volume
|
||||
const steps = 50
|
||||
const stepTime = duration / steps
|
||||
const volumeStep = targetVolume / steps
|
||||
|
||||
let currentStep = 0
|
||||
this.fadeInterval = setInterval(() => {
|
||||
if (currentStep >= steps || !this.audio) {
|
||||
if (this.fadeInterval) {
|
||||
clearInterval(this.fadeInterval)
|
||||
this.fadeInterval = null
|
||||
}
|
||||
if (this.audio) {
|
||||
this.audio.volume = targetVolume
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.audio.volume = Math.min(volumeStep * currentStep, targetVolume)
|
||||
currentStep++
|
||||
}, stepTime)
|
||||
}
|
||||
|
||||
// 淡出效果
|
||||
private fadeOut(duration = 1000) {
|
||||
if (!this.audio) return
|
||||
|
||||
const startVolume = this.audio.volume
|
||||
const steps = 50
|
||||
const stepTime = duration / steps
|
||||
const volumeStep = startVolume / steps
|
||||
|
||||
let currentStep = 0
|
||||
this.fadeInterval = setInterval(() => {
|
||||
if (currentStep >= steps || !this.audio) {
|
||||
if (this.fadeInterval) {
|
||||
clearInterval(this.fadeInterval)
|
||||
this.fadeInterval = null
|
||||
}
|
||||
if (this.audio) {
|
||||
this.audio.pause()
|
||||
this.audio.currentTime = 0 // 重置播放位置
|
||||
this.audio.volume = this.volume // 恢復原始音量
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.audio.volume = Math.max(startVolume - volumeStep * currentStep, 0)
|
||||
currentStep++
|
||||
}, stepTime)
|
||||
}
|
||||
|
||||
async start() {
|
||||
// 清除任何進行中的淡出效果
|
||||
if (this.fadeInterval) {
|
||||
clearInterval(this.fadeInterval)
|
||||
this.fadeInterval = null
|
||||
}
|
||||
|
||||
// 如果音頻對象不存在,重新初始化
|
||||
if (!this.audio) {
|
||||
this.initAudio()
|
||||
// 等待一下讓音頻對象初始化完成
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
if (!this.audio) return
|
||||
|
||||
try {
|
||||
this.enabled = true
|
||||
this.isPlaying = true
|
||||
this.saveState()
|
||||
|
||||
// 確保音頻對象處於正確狀態
|
||||
this.audio.currentTime = 0
|
||||
this.audio.volume = 0 // 從0開始,準備淡入
|
||||
|
||||
// 開始播放並淡入
|
||||
await this.audio.play()
|
||||
this.fadeIn(2000)
|
||||
} catch (error) {
|
||||
// 播放失敗,重新初始化並重試
|
||||
this.reinitAudio()
|
||||
this.isPlaying = false
|
||||
this.enabled = false
|
||||
this.saveState()
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.audio) return
|
||||
|
||||
this.enabled = false
|
||||
this.isPlaying = false
|
||||
this.saveState()
|
||||
|
||||
// 清除淡入效果
|
||||
if (this.fadeInterval) {
|
||||
clearInterval(this.fadeInterval)
|
||||
this.fadeInterval = null
|
||||
}
|
||||
|
||||
// 淡出並停止
|
||||
this.fadeOut(1000)
|
||||
}
|
||||
|
||||
setVolume(volume: number) {
|
||||
this.volume = Math.max(0, Math.min(1, volume))
|
||||
if (this.audio && this.isPlaying) {
|
||||
this.audio.volume = this.volume
|
||||
}
|
||||
this.saveState()
|
||||
}
|
||||
|
||||
getVolume() {
|
||||
return this.volume
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.enabled
|
||||
}
|
||||
|
||||
getIsPlaying() {
|
||||
return this.isPlaying && this.audio && !this.audio.paused
|
||||
}
|
||||
|
||||
// 獲取當前狀態
|
||||
getState() {
|
||||
return {
|
||||
isPlaying: this.getIsPlaying(),
|
||||
enabled: this.enabled,
|
||||
volume: this.volume,
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取音樂資訊
|
||||
getMusicInfo() {
|
||||
if (!this.audio) return null
|
||||
|
||||
return {
|
||||
duration: this.audio.duration || 0,
|
||||
currentTime: this.audio.currentTime || 0,
|
||||
loaded: this.audio.readyState >= 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全局背景音樂管理器
|
||||
export const backgroundMusicManager = new BackgroundMusicManager()
|
378
lib/categorization.ts
Normal file
378
lib/categorization.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
// 工作痛點分類系統 - 精簡版 10 個分類
|
||||
export const categories = [
|
||||
{
|
||||
name: "重複作業",
|
||||
description: "每天遇到都在做的重工、報表、開票等例行事務",
|
||||
keywords: [
|
||||
"重複",
|
||||
"重工",
|
||||
"報表",
|
||||
"開票",
|
||||
"例行",
|
||||
"每天",
|
||||
"固定",
|
||||
"重複性",
|
||||
"例行公事",
|
||||
"重複工作",
|
||||
"日常作業",
|
||||
"定期",
|
||||
"週期性",
|
||||
"重複操作",
|
||||
"機械化",
|
||||
"單調",
|
||||
],
|
||||
color: "#3B82F6",
|
||||
bgColor: "from-blue-500/20 to-blue-600/20",
|
||||
borderColor: "border-blue-400/30",
|
||||
textColor: "text-blue-200",
|
||||
icon: "🔄",
|
||||
},
|
||||
{
|
||||
name: "數據混亂",
|
||||
description: "資料統整困難、爆表、無法分析或視覺化",
|
||||
keywords: [
|
||||
"數據",
|
||||
"資料",
|
||||
"統整",
|
||||
"爆表",
|
||||
"分析",
|
||||
"視覺化",
|
||||
"整理",
|
||||
"混亂",
|
||||
"散亂",
|
||||
"不一致",
|
||||
"格式",
|
||||
"匯入",
|
||||
"匯出",
|
||||
"Excel",
|
||||
"CSV",
|
||||
"報告",
|
||||
"圖表",
|
||||
],
|
||||
color: "#EC4899",
|
||||
bgColor: "from-pink-500/20 to-rose-600/20",
|
||||
borderColor: "border-pink-400/30",
|
||||
textColor: "text-pink-200",
|
||||
icon: "📊",
|
||||
},
|
||||
{
|
||||
name: "找不到資料",
|
||||
description: "文件、SOP、規則等資訊難查找",
|
||||
keywords: [
|
||||
"找不到",
|
||||
"文件",
|
||||
"SOP",
|
||||
"規則",
|
||||
"資訊",
|
||||
"查找",
|
||||
"搜尋",
|
||||
"文檔",
|
||||
"手冊",
|
||||
"說明",
|
||||
"指引",
|
||||
"標準",
|
||||
"程序",
|
||||
"知識",
|
||||
"資料庫",
|
||||
"檔案",
|
||||
"位置",
|
||||
],
|
||||
color: "#F59E0B",
|
||||
bgColor: "from-yellow-500/20 to-amber-600/20",
|
||||
borderColor: "border-yellow-400/30",
|
||||
textColor: "text-yellow-200",
|
||||
icon: "🔍",
|
||||
},
|
||||
{
|
||||
name: "流程卡關",
|
||||
description: "請購、採購、維修等流程追蹤不到",
|
||||
keywords: [
|
||||
"流程",
|
||||
"請購",
|
||||
"採購",
|
||||
"維修",
|
||||
"追蹤",
|
||||
"卡關",
|
||||
"停滯",
|
||||
"延遲",
|
||||
"審核",
|
||||
"簽核",
|
||||
"申請",
|
||||
"進度",
|
||||
"狀態",
|
||||
"追不到",
|
||||
"不知道",
|
||||
"等待",
|
||||
],
|
||||
color: "#EF4444",
|
||||
bgColor: "from-red-500/20 to-rose-600/20",
|
||||
borderColor: "border-red-400/30",
|
||||
textColor: "text-red-200",
|
||||
icon: "🚧",
|
||||
},
|
||||
{
|
||||
name: "文件太多",
|
||||
description: "表單量、會議紀錄等需人工輸入/比對",
|
||||
keywords: [
|
||||
"文件",
|
||||
"表單",
|
||||
"會議紀錄",
|
||||
"人工輸入",
|
||||
"比對",
|
||||
"繁瑣",
|
||||
"太多",
|
||||
"大量",
|
||||
"手動",
|
||||
"輸入",
|
||||
"填寫",
|
||||
"紙本",
|
||||
"列印",
|
||||
"掃描",
|
||||
"歸檔",
|
||||
"整理",
|
||||
],
|
||||
color: "#8B5CF6",
|
||||
bgColor: "from-purple-500/20 to-violet-600/20",
|
||||
borderColor: "border-purple-400/30",
|
||||
textColor: "text-purple-200",
|
||||
icon: "📄",
|
||||
},
|
||||
{
|
||||
name: "協作混亂",
|
||||
description: "人工配置、斷線、跨部門難協調",
|
||||
keywords: [
|
||||
"協作",
|
||||
"人工配置",
|
||||
"斷線",
|
||||
"跨部門",
|
||||
"協調",
|
||||
"溝通",
|
||||
"配合",
|
||||
"團隊",
|
||||
"合作",
|
||||
"聯繫",
|
||||
"回應",
|
||||
"討論",
|
||||
"會議",
|
||||
"同事",
|
||||
"部門",
|
||||
"協調",
|
||||
],
|
||||
color: "#10B981",
|
||||
bgColor: "from-green-500/20 to-emerald-600/20",
|
||||
borderColor: "border-green-400/30",
|
||||
textColor: "text-green-200",
|
||||
icon: "🤝",
|
||||
},
|
||||
{
|
||||
name: "時間浪費",
|
||||
description: "等資料、等簽核、等開會系統等無效率時間",
|
||||
keywords: [
|
||||
"時間",
|
||||
"等資料",
|
||||
"等簽核",
|
||||
"等開會",
|
||||
"無效率",
|
||||
"浪費",
|
||||
"等待",
|
||||
"拖延",
|
||||
"慢",
|
||||
"效率",
|
||||
"速度",
|
||||
"加班",
|
||||
"忙碌",
|
||||
"趕工",
|
||||
"deadline",
|
||||
"截止",
|
||||
],
|
||||
color: "#F97316",
|
||||
bgColor: "from-orange-500/20 to-red-600/20",
|
||||
borderColor: "border-orange-400/30",
|
||||
textColor: "text-orange-200",
|
||||
icon: "⏰",
|
||||
},
|
||||
{
|
||||
name: "諮詢煩重",
|
||||
description: "客戶或內部常見問答人力吃緊",
|
||||
keywords: [
|
||||
"諮詢",
|
||||
"客戶",
|
||||
"內部",
|
||||
"問答",
|
||||
"人力吃緊",
|
||||
"常見問題",
|
||||
"重複問題",
|
||||
"客服",
|
||||
"支援",
|
||||
"解答",
|
||||
"回覆",
|
||||
"詢問",
|
||||
"疑問",
|
||||
"FAQ",
|
||||
"服務",
|
||||
"處理",
|
||||
],
|
||||
color: "#06B6D4",
|
||||
bgColor: "from-cyan-500/20 to-teal-600/20",
|
||||
borderColor: "border-cyan-400/30",
|
||||
textColor: "text-cyan-200",
|
||||
icon: "💬",
|
||||
},
|
||||
{
|
||||
name: "權限不清",
|
||||
description: "權限管理混亂、責任不明",
|
||||
keywords: [
|
||||
"權限",
|
||||
"管理",
|
||||
"混亂",
|
||||
"責任",
|
||||
"不明",
|
||||
"不清楚",
|
||||
"授權",
|
||||
"存取",
|
||||
"帳號",
|
||||
"密碼",
|
||||
"登入",
|
||||
"角色",
|
||||
"職責",
|
||||
"負責人",
|
||||
"歸屬",
|
||||
"分工",
|
||||
],
|
||||
color: "#84CC16",
|
||||
bgColor: "from-lime-500/20 to-green-600/20",
|
||||
borderColor: "border-lime-400/30",
|
||||
textColor: "text-lime-200",
|
||||
icon: "🔐",
|
||||
},
|
||||
{
|
||||
name: "系統太多",
|
||||
description: "多系統操作、切換成本高",
|
||||
keywords: [
|
||||
"系統",
|
||||
"多系統",
|
||||
"切換",
|
||||
"成本高",
|
||||
"太多",
|
||||
"太雜",
|
||||
"操作",
|
||||
"介面",
|
||||
"平台",
|
||||
"工具",
|
||||
"軟體",
|
||||
"應用程式",
|
||||
"登入",
|
||||
"帳號",
|
||||
"整合",
|
||||
"統一",
|
||||
],
|
||||
color: "#6366F1",
|
||||
bgColor: "from-indigo-500/20 to-purple-600/20",
|
||||
borderColor: "border-indigo-400/30",
|
||||
textColor: "text-indigo-200",
|
||||
icon: "💻",
|
||||
},
|
||||
]
|
||||
|
||||
export interface Wish {
|
||||
id: number
|
||||
title: string
|
||||
currentPain: string
|
||||
expectedSolution: string
|
||||
expectedEffect: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
name: string
|
||||
description: string
|
||||
keywords: string[]
|
||||
color: string
|
||||
bgColor: string
|
||||
borderColor: string
|
||||
textColor: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
// 多標籤自動分類函數 - 最多返回3個
|
||||
export function categorizeWishMultiple(wish: Wish): Category[] {
|
||||
const fullText = `${wish.title} ${wish.currentPain} ${wish.expectedSolution} ${wish.expectedEffect}`.toLowerCase()
|
||||
const matchedCategories: Category[] = []
|
||||
|
||||
// 檢查每個分類,收集所有匹配的分類
|
||||
categories.forEach((category) => {
|
||||
const matchedKeywords = category.keywords.filter((keyword) => fullText.includes(keyword.toLowerCase()))
|
||||
|
||||
if (matchedKeywords.length > 0) {
|
||||
matchedCategories.push({
|
||||
...category,
|
||||
matchScore: matchedKeywords.length, // 添加匹配分數
|
||||
} as Category & { matchScore: number })
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有匹配的分類,按匹配分數排序並返回最多3個
|
||||
if (matchedCategories.length > 0) {
|
||||
return matchedCategories.sort((a, b) => (b as any).matchScore - (a as any).matchScore).slice(0, 3) // 最多3個標籤
|
||||
}
|
||||
|
||||
// 如果沒有匹配到任何分類,返回默認分類
|
||||
return [
|
||||
{
|
||||
name: "其他問題",
|
||||
description: "未能歸類的特殊工作困擾",
|
||||
keywords: [],
|
||||
color: "#6B7280",
|
||||
bgColor: "from-gray-500/20 to-slate-600/20",
|
||||
borderColor: "border-gray-400/30",
|
||||
textColor: "text-gray-200",
|
||||
icon: "❓",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// 保持向後兼容的單一分類函數
|
||||
export function categorizeWish(wish: Wish): Category {
|
||||
const categories = categorizeWishMultiple(wish)
|
||||
return categories[0] // 返回最匹配的分類
|
||||
}
|
||||
|
||||
// 獲取所有願望的分類統計(支持多標籤)
|
||||
export function getCategoryStats(wishes: Wish[]) {
|
||||
const stats: { [key: string]: number } = {}
|
||||
|
||||
// 初始化統計
|
||||
categories.forEach((cat) => {
|
||||
stats[cat.name] = 0
|
||||
})
|
||||
stats["其他問題"] = 0
|
||||
|
||||
// 統計每個分類的數量(多標籤計算)
|
||||
wishes.forEach((wish) => {
|
||||
const wishCategories = categorizeWishMultiple(wish)
|
||||
wishCategories.forEach((category) => {
|
||||
stats[category.name]++
|
||||
})
|
||||
})
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// 獲取分類統計(用於雷達圖,避免重複計算)
|
||||
export function getCategoryStatsForRadar(wishes: Wish[]) {
|
||||
const stats: { [key: string]: number } = {}
|
||||
|
||||
// 初始化統計
|
||||
categories.forEach((cat) => {
|
||||
stats[cat.name] = 0
|
||||
})
|
||||
stats["其他問題"] = 0
|
||||
|
||||
// 統計每個分類的數量(每個願望只計算一次,取主要分類)
|
||||
wishes.forEach((wish) => {
|
||||
const primaryCategory = categorizeWish(wish)
|
||||
stats[primaryCategory.name]++
|
||||
})
|
||||
|
||||
return stats
|
||||
}
|
259
lib/solution-recommendations.ts
Normal file
259
lib/solution-recommendations.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
// 智能解決方案推薦系統
|
||||
export interface SolutionCategory {
|
||||
id: string
|
||||
name: string
|
||||
icon: string
|
||||
description: string
|
||||
color: string
|
||||
bgColor: string
|
||||
borderColor: string
|
||||
textColor: string
|
||||
keywords: string[]
|
||||
examples: string[]
|
||||
benefits: string[]
|
||||
difficulty: "easy" | "medium" | "hard"
|
||||
timeframe: string
|
||||
techStack: string[]
|
||||
}
|
||||
|
||||
export const solutionCategories: SolutionCategory[] = [
|
||||
{
|
||||
id: "chatbot",
|
||||
name: "聊天機器人",
|
||||
icon: "🤖",
|
||||
description: "建立對話式助手,回答常見問題、流程說明",
|
||||
color: "#3B82F6",
|
||||
bgColor: "from-blue-500/20 to-blue-600/20",
|
||||
borderColor: "border-blue-400/30",
|
||||
textColor: "text-blue-200",
|
||||
keywords: ["問答", "諮詢", "客服", "常見問題", "FAQ", "詢問", "回覆", "解答", "支援", "服務"],
|
||||
examples: ["客戶、內部SOP詢問", "流程說明"],
|
||||
benefits: ["24小時服務", "減少重複問答", "提升回應效率"],
|
||||
difficulty: "medium",
|
||||
timeframe: "2-4週",
|
||||
techStack: ["ChatGPT", "Dialogflow", "LINE Bot"],
|
||||
},
|
||||
{
|
||||
id: "document-generation",
|
||||
name: "文件生成",
|
||||
icon: "📄",
|
||||
description: "自動產出報告、會議紀錄、表單填寫等",
|
||||
color: "#10B981",
|
||||
bgColor: "from-green-500/20 to-emerald-600/20",
|
||||
borderColor: "border-green-400/30",
|
||||
textColor: "text-green-200",
|
||||
keywords: ["報表", "文件", "表單", "會議紀錄", "填寫", "產出", "生成", "製作", "撰寫", "輸出"],
|
||||
examples: ["報告填寫", "表單填入", "合約產製"],
|
||||
benefits: ["節省撰寫時間", "格式統一", "減少錯誤"],
|
||||
difficulty: "easy",
|
||||
timeframe: "1-2週",
|
||||
techStack: ["Word模板", "Google Docs API", "PDF生成"],
|
||||
},
|
||||
{
|
||||
id: "knowledge-management",
|
||||
name: "知識整理",
|
||||
icon: "📚",
|
||||
description: "提取與重組非結構資料,打造企業知識庫",
|
||||
color: "#8B5CF6",
|
||||
bgColor: "from-purple-500/20 to-violet-600/20",
|
||||
borderColor: "border-purple-400/30",
|
||||
textColor: "text-purple-200",
|
||||
keywords: ["知識", "整理", "歸檔", "分類", "搜尋", "文檔", "資料庫", "管理", "查找", "SOP"],
|
||||
examples: ["FAQ建立", "SOP匯整", "文件歸檔"],
|
||||
benefits: ["知識不流失", "快速查找", "經驗傳承"],
|
||||
difficulty: "medium",
|
||||
timeframe: "3-6週",
|
||||
techStack: ["Notion", "Confluence", "向量資料庫"],
|
||||
},
|
||||
{
|
||||
id: "data-visualization",
|
||||
name: "數據視覺化",
|
||||
icon: "📊",
|
||||
description: "把複雜數據轉換成圖表、儀表板",
|
||||
color: "#F59E0B",
|
||||
bgColor: "from-yellow-500/20 to-amber-600/20",
|
||||
borderColor: "border-yellow-400/30",
|
||||
textColor: "text-yellow-200",
|
||||
keywords: ["數據", "資料", "圖表", "分析", "統計", "視覺化", "儀表板", "報告", "趨勢", "爆表"],
|
||||
examples: ["報表分析", "自動儀表板", "績效追蹤"],
|
||||
benefits: ["一目了然", "決策支援", "趨勢洞察"],
|
||||
difficulty: "medium",
|
||||
timeframe: "2-4週",
|
||||
techStack: ["Power BI", "Tableau", "Google Charts"],
|
||||
},
|
||||
{
|
||||
id: "process-automation",
|
||||
name: "流程自動化",
|
||||
icon: "⚡",
|
||||
description: "用RPA/n8n/Zapier等自動執行例行操作",
|
||||
color: "#EF4444",
|
||||
bgColor: "from-red-500/20 to-rose-600/20",
|
||||
borderColor: "border-red-400/30",
|
||||
textColor: "text-red-200",
|
||||
keywords: ["自動化", "流程", "重複", "例行", "操作", "執行", "RPA", "機器人", "排程", "批次"],
|
||||
examples: ["表單自動送簽", "報表排程產製", "系統跳轉操作"],
|
||||
benefits: ["解放人力", "減少錯誤", "提升效率"],
|
||||
difficulty: "hard",
|
||||
timeframe: "4-8週",
|
||||
techStack: ["UiPath", "n8n", "Zapier", "Power Automate"],
|
||||
},
|
||||
{
|
||||
id: "data-cleaning",
|
||||
name: "資料清洗",
|
||||
icon: "🧹",
|
||||
description: "格式轉換、資料對齊、電位補齊與比對",
|
||||
color: "#06B6D4",
|
||||
bgColor: "from-cyan-500/20 to-teal-600/20",
|
||||
borderColor: "border-cyan-400/30",
|
||||
textColor: "text-cyan-200",
|
||||
keywords: ["資料", "清洗", "格式", "轉換", "對齊", "比對", "Excel", "CSV", "匯入", "匯出"],
|
||||
examples: ["Excel合併", "去重", "比對鎖定", "轉格式"],
|
||||
benefits: ["資料品質提升", "格式統一", "節省手工時間"],
|
||||
difficulty: "easy",
|
||||
timeframe: "1-3週",
|
||||
techStack: ["Python", "Excel VBA", "Power Query"],
|
||||
},
|
||||
{
|
||||
id: "ai-search",
|
||||
name: "AI搜尋推薦",
|
||||
icon: "🔍",
|
||||
description: "讓使用者快速找到相關知識、紀錄或案例",
|
||||
color: "#84CC16",
|
||||
bgColor: "from-lime-500/20 to-green-600/20",
|
||||
borderColor: "border-lime-400/30",
|
||||
textColor: "text-lime-200",
|
||||
keywords: ["搜尋", "查找", "推薦", "相關", "案例", "知識", "文件", "快速", "智能", "找到"],
|
||||
examples: ["文件搜尋", "產品推薦", "知識引導"],
|
||||
benefits: ["快速定位", "智能推薦", "提升查找效率"],
|
||||
difficulty: "hard",
|
||||
timeframe: "4-6週",
|
||||
techStack: ["Elasticsearch", "AI向量搜尋", "RAG系統"],
|
||||
},
|
||||
{
|
||||
id: "image-recognition",
|
||||
name: "影像辨識",
|
||||
icon: "👁️",
|
||||
description: "分析票片、文件、物件、標籤等",
|
||||
color: "#EC4899",
|
||||
bgColor: "from-pink-500/20 to-rose-600/20",
|
||||
borderColor: "border-pink-400/30",
|
||||
textColor: "text-pink-200",
|
||||
keywords: ["影像", "辨識", "掃描", "票據", "文件", "照片", "圖片", "識別", "分析", "OCR"],
|
||||
examples: ["缺陷檢測", "清單金額讀取", "監控畫面分析"],
|
||||
benefits: ["自動識別", "減少人工檢查", "提升準確度"],
|
||||
difficulty: "hard",
|
||||
timeframe: "6-10週",
|
||||
techStack: ["OpenCV", "TensorFlow", "Azure Vision"],
|
||||
},
|
||||
{
|
||||
id: "smart-summary",
|
||||
name: "智能摘要",
|
||||
icon: "📝",
|
||||
description: "將會議/文件自動摘要成精華",
|
||||
color: "#F97316",
|
||||
bgColor: "from-orange-500/20 to-red-600/20",
|
||||
borderColor: "border-orange-400/30",
|
||||
textColor: "text-orange-200",
|
||||
keywords: ["摘要", "會議", "文件", "精華", "重點", "總結", "整理", "濃縮", "提取", "歸納"],
|
||||
examples: ["會議紀錄", "長文件摘要整理"],
|
||||
benefits: ["快速掌握重點", "節省閱讀時間", "重點不遺漏"],
|
||||
difficulty: "medium",
|
||||
timeframe: "2-4週",
|
||||
techStack: ["GPT-4", "Claude", "自然語言處理"],
|
||||
},
|
||||
{
|
||||
id: "system-integration",
|
||||
name: "系統整合",
|
||||
icon: "🔗",
|
||||
description: "多系統資料打通、統一操作",
|
||||
color: "#6366F1",
|
||||
bgColor: "from-indigo-500/20 to-purple-600/20",
|
||||
borderColor: "border-indigo-400/30",
|
||||
textColor: "text-indigo-200",
|
||||
keywords: ["系統", "整合", "打通", "統一", "多系統", "切換", "介面", "平台", "連接", "同步"],
|
||||
examples: ["ERP + CRM串接", "跨平台整合操作"],
|
||||
benefits: ["減少重複輸入", "資料同步", "操作統一"],
|
||||
difficulty: "hard",
|
||||
timeframe: "8-12週",
|
||||
techStack: ["API整合", "ETL工具", "中介軟體"],
|
||||
},
|
||||
]
|
||||
|
||||
// 智能推薦引擎
|
||||
export function generateSolutionRecommendations(wish: any): {
|
||||
recommendations: SolutionCategory[]
|
||||
personalizedMessage: string
|
||||
confidence: number
|
||||
} {
|
||||
const fullText = `${wish.title} ${wish.currentPain} ${wish.expectedSolution} ${wish.expectedEffect}`.toLowerCase()
|
||||
|
||||
// 計算每個解決方案的匹配分數
|
||||
const scoredSolutions = solutionCategories
|
||||
.map((solution) => {
|
||||
const matchedKeywords = solution.keywords.filter((keyword) => fullText.includes(keyword.toLowerCase()))
|
||||
const score = matchedKeywords.length
|
||||
return { solution, score, matchedKeywords }
|
||||
})
|
||||
.filter((item) => item.score > 0)
|
||||
|
||||
// 按分數排序,取前3個
|
||||
const topSolutions = scoredSolutions
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 3)
|
||||
.map((item) => item.solution)
|
||||
|
||||
// 生成個人化訊息
|
||||
const personalizedMessage = generatePersonalizedMessage(wish, topSolutions, scoredSolutions[0]?.matchedKeywords || [])
|
||||
|
||||
// 計算信心度
|
||||
const confidence = Math.min(95, Math.max(60, scoredSolutions[0]?.score * 15 || 60))
|
||||
|
||||
return {
|
||||
recommendations: topSolutions,
|
||||
personalizedMessage,
|
||||
confidence,
|
||||
}
|
||||
}
|
||||
|
||||
// 生成溫暖人性化的回覆訊息
|
||||
function generatePersonalizedMessage(wish: any, solutions: SolutionCategory[], matchedKeywords: string[]): string {
|
||||
const greetings = [
|
||||
"看到你的困擾,我很能理解這種感受",
|
||||
"這個問題確實很常見,你不是一個人在面對",
|
||||
"感謝你願意分享這個困擾,讓我來幫你想想解決方案",
|
||||
"我仔細看了你的描述,這確實是個需要解決的問題",
|
||||
]
|
||||
|
||||
const empathyPhrases = [
|
||||
"工作中遇到這樣的狀況真的很讓人頭疼",
|
||||
"我完全理解這種重複性工作帶來的疲憊感",
|
||||
"這種效率低下的情況確實會影響工作心情",
|
||||
"面對這樣的挑戰,任何人都會感到困擾",
|
||||
]
|
||||
|
||||
const solutionIntros = [
|
||||
"根據你的描述,我認為以下幾個方向可能會對你有幫助:",
|
||||
"結合你的具體情況,我建議可以考慮這些解決方案:",
|
||||
"針對你提到的問題,我整理了幾個可行的改善方向:",
|
||||
"基於我的經驗,這類問題通常可以透過以下方式來改善:",
|
||||
]
|
||||
|
||||
const encouragements = [
|
||||
"相信透過合適的工具和方法,這個問題是可以得到很好的改善的!",
|
||||
"每個困擾都是改善的機會,你已經踏出了重要的第一步。",
|
||||
"不要氣餒,很多看似複雜的問題其實都有相對簡單的解決方案。",
|
||||
"你的這個想法很棒,讓我們一起找到最適合的解決方式吧!",
|
||||
]
|
||||
|
||||
const greeting = greetings[Math.floor(Math.random() * greetings.length)]
|
||||
const empathy = empathyPhrases[Math.floor(Math.random() * empathyPhrases.length)]
|
||||
const solutionIntro = solutionIntros[Math.floor(Math.random() * solutionIntros.length)]
|
||||
const encouragement = encouragements[Math.floor(Math.random() * encouragements.length)]
|
||||
|
||||
return `${greeting}。${empathy}。\n\n${solutionIntro}\n\n${encouragement}`
|
||||
}
|
||||
|
||||
// 獲取解決方案的詳細建議
|
||||
export function getSolutionDetails(solutionId: string): SolutionCategory | null {
|
||||
return solutionCategories.find((solution) => solution.id === solutionId) || null
|
||||
}
|
121
lib/sound-effects.ts
Normal file
121
lib/sound-effects.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
// 更溫柔的音效管理系統
|
||||
class SoundManager {
|
||||
private audioContext: AudioContext | null = null
|
||||
private enabled = true
|
||||
|
||||
constructor() {
|
||||
this.initAudioContext()
|
||||
}
|
||||
|
||||
private async initAudioContext() {
|
||||
try {
|
||||
this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
|
||||
} catch (error) {
|
||||
console.log("Audio not supported")
|
||||
}
|
||||
}
|
||||
|
||||
// 創建溫柔的鈴聲效果
|
||||
private async createGentleBell(frequency = 800, duration = 0.3) {
|
||||
if (!this.audioContext) return
|
||||
|
||||
const oscillator = this.audioContext.createOscillator()
|
||||
const gainNode = this.audioContext.createGain()
|
||||
const filterNode = this.audioContext.createBiquadFilter()
|
||||
|
||||
// 使用正弦波創建溫柔的音調
|
||||
oscillator.type = "sine"
|
||||
oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime)
|
||||
|
||||
// 低通濾波器讓聲音更柔和
|
||||
filterNode.type = "lowpass"
|
||||
filterNode.frequency.setValueAtTime(2000, this.audioContext.currentTime)
|
||||
filterNode.Q.setValueAtTime(1, this.audioContext.currentTime)
|
||||
|
||||
// 溫柔的音量包絡
|
||||
gainNode.gain.setValueAtTime(0, this.audioContext.currentTime)
|
||||
gainNode.gain.linearRampToValueAtTime(0.1, this.audioContext.currentTime + 0.01)
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration)
|
||||
|
||||
// 連接音頻節點
|
||||
oscillator.connect(filterNode)
|
||||
filterNode.connect(gainNode)
|
||||
gainNode.connect(this.audioContext.destination)
|
||||
|
||||
// 播放音效
|
||||
oscillator.start(this.audioContext.currentTime)
|
||||
oscillator.stop(this.audioContext.currentTime + duration)
|
||||
}
|
||||
|
||||
// 創建溫柔的和弦
|
||||
private async createGentleChord(frequencies: number[], duration = 0.5) {
|
||||
if (!this.audioContext) return
|
||||
|
||||
frequencies.forEach((freq, index) => {
|
||||
setTimeout(() => {
|
||||
this.createGentleBell(freq, duration)
|
||||
}, index * 50) // 輕微延遲創造和弦效果
|
||||
})
|
||||
}
|
||||
|
||||
// 創建水滴聲效果
|
||||
private async createWaterDrop() {
|
||||
if (!this.audioContext) return
|
||||
|
||||
const oscillator = this.audioContext.createOscillator()
|
||||
const gainNode = this.audioContext.createGain()
|
||||
|
||||
oscillator.type = "sine"
|
||||
oscillator.frequency.setValueAtTime(1200, this.audioContext.currentTime)
|
||||
oscillator.frequency.exponentialRampToValueAtTime(400, this.audioContext.currentTime + 0.1)
|
||||
|
||||
gainNode.gain.setValueAtTime(0, this.audioContext.currentTime)
|
||||
gainNode.gain.linearRampToValueAtTime(0.08, this.audioContext.currentTime + 0.01)
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.15)
|
||||
|
||||
oscillator.connect(gainNode)
|
||||
gainNode.connect(this.audioContext.destination)
|
||||
|
||||
oscillator.start(this.audioContext.currentTime)
|
||||
oscillator.stop(this.audioContext.currentTime + 0.15)
|
||||
}
|
||||
|
||||
async play(soundName: string) {
|
||||
if (!this.enabled || !this.audioContext) return
|
||||
|
||||
try {
|
||||
// 確保音頻上下文已啟動
|
||||
if (this.audioContext.state === "suspended") {
|
||||
await this.audioContext.resume()
|
||||
}
|
||||
|
||||
switch (soundName) {
|
||||
case "click":
|
||||
this.createWaterDrop()
|
||||
break
|
||||
case "submit":
|
||||
this.createGentleChord([523, 659, 784], 0.6) // C大調和弦
|
||||
break
|
||||
case "success":
|
||||
this.createGentleChord([523, 659, 784, 988], 0.8) // 更豐富的和弦
|
||||
break
|
||||
case "hover":
|
||||
this.createGentleBell(1000, 0.1)
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
// 靜默處理錯誤
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.enabled = !this.enabled
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.enabled
|
||||
}
|
||||
}
|
||||
|
||||
// 全局音效管理器
|
||||
export const soundManager = new SoundManager()
|
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal 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))
|
||||
}
|
Reference in New Issue
Block a user