@mazees/mark 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +209 -0
  3. package/bin/mark.js +27 -0
  4. package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
  5. package/out/renderer/assets/index-G2fADWO-.js +4768 -0
  6. package/out/renderer/assets/index-f3mVse3c.css +1 -0
  7. package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
  8. package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
  9. package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
  10. package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
  11. package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
  12. package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
  13. package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
  14. package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
  15. package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
  16. package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  17. package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  18. package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  19. package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  20. package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  21. package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  22. package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  23. package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  24. package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
  25. package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
  26. package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
  27. package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
  28. package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
  29. package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
  30. package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
  31. package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
  32. package/out/renderer/favicon.ico +0 -0
  33. package/out/renderer/icon-256.png +0 -0
  34. package/out/renderer/icon.svg +1 -0
  35. package/out/renderer/index.html +25 -0
  36. package/out/renderer/manifest.json +25 -0
  37. package/out/renderer/robot-simulator.html +1640 -0
  38. package/package.json +106 -0
  39. package/src/cli/index.js +332 -0
  40. package/src/cli/monitor.js +413 -0
  41. package/src/cli/theme.js +347 -0
  42. package/src/launcher/MarkTray.cs +227 -0
  43. package/src/main/browser-agent.js +1132 -0
  44. package/src/main/git-service.js +90 -0
  45. package/src/main/google/google-calendar.js +69 -0
  46. package/src/main/google/google-drive.js +263 -0
  47. package/src/main/google/google-gmail.js +131 -0
  48. package/src/main/google/google-service.js +241 -0
  49. package/src/main/node-tools.js +23 -0
  50. package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
  51. package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
  52. package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
  53. package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
  54. package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
  55. package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
  56. package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
  57. package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
  58. package/src/main/plugins/plugin-loader.js +306 -0
  59. package/src/main/skills/skill-manager.js +265 -0
  60. package/src/main/syntax-validator.js +455 -0
  61. package/src/main/task-daemon.js +117 -0
  62. package/src/main/telegram/telegram-service.js +835 -0
  63. package/src/main/tools/browser-tools.js +516 -0
  64. package/src/main/tools/file-tools.js +1126 -0
  65. package/src/main/tools/git-tools.js +79 -0
  66. package/src/main/tools/google-tools.js +566 -0
  67. package/src/main/tools/index.js +36 -0
  68. package/src/main/tools/media-tools.js +42 -0
  69. package/src/main/tools/system-tools.js +295 -0
  70. package/src/main/tools/task-tools.js +73 -0
  71. package/src/main/tools/telegram-tools.js +101 -0
  72. package/src/renderer/index.html +24 -0
  73. package/src/renderer/public/favicon.ico +0 -0
  74. package/src/renderer/public/icon-256.png +0 -0
  75. package/src/renderer/public/icon.svg +1 -0
  76. package/src/renderer/public/manifest.json +25 -0
  77. package/src/renderer/public/robot-simulator.html +1640 -0
  78. package/src/renderer/src/App.jsx +304 -0
  79. package/src/renderer/src/api/ai/awareness.js +294 -0
  80. package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
  81. package/src/renderer/src/api/ai/contextCompactor.js +129 -0
  82. package/src/renderer/src/api/ai/contextManager.js +1130 -0
  83. package/src/renderer/src/api/ai/core.js +167 -0
  84. package/src/renderer/src/api/ai/index.js +4 -0
  85. package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
  86. package/src/renderer/src/api/ai/persona.js +115 -0
  87. package/src/renderer/src/api/ai/planning.js +317 -0
  88. package/src/renderer/src/api/ai/relationship.js +177 -0
  89. package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
  90. package/src/renderer/src/api/ai/tools.js +167 -0
  91. package/src/renderer/src/api/ai/utils.js +359 -0
  92. package/src/renderer/src/api/db.js +1013 -0
  93. package/src/renderer/src/api/oramaStore.js +592 -0
  94. package/src/renderer/src/api/ragPipeline.js +87 -0
  95. package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
  96. package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
  97. package/src/renderer/src/api/subagent/subagentStore.js +173 -0
  98. package/src/renderer/src/api/taskExecutor.js +18 -0
  99. package/src/renderer/src/api/taskStore.js +315 -0
  100. package/src/renderer/src/api/tools/core-tools.js +707 -0
  101. package/src/renderer/src/api/tools/index.js +151 -0
  102. package/src/renderer/src/api/turnPairMigrator.js +200 -0
  103. package/src/renderer/src/api/vectorMemory.js +136 -0
  104. package/src/renderer/src/api/wakeWord.js +101 -0
  105. package/src/renderer/src/api/web-bridge.js +885 -0
  106. package/src/renderer/src/api/webSpeech.js +178 -0
  107. package/src/renderer/src/api/workspaceRag.js +76 -0
  108. package/src/renderer/src/assets/electron.svg +10 -0
  109. package/src/renderer/src/assets/icon.svg +1 -0
  110. package/src/renderer/src/assets/main.css +589 -0
  111. package/src/renderer/src/assets/music-cover.png +0 -0
  112. package/src/renderer/src/assets/wavy-lines.svg +25 -0
  113. package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
  114. package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
  115. package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
  116. package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
  117. package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
  118. package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
  119. package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
  120. package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
  121. package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
  122. package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
  123. package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
  124. package/src/renderer/src/components/Chat/index.js +11 -0
  125. package/src/renderer/src/components/ChatList.jsx +434 -0
  126. package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
  127. package/src/renderer/src/components/Versions.jsx +15 -0
  128. package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
  129. package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
  130. package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
  131. package/src/renderer/src/components/config/CameraSection.jsx +100 -0
  132. package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
  133. package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
  134. package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
  135. package/src/renderer/src/components/config/index.js +5 -0
  136. package/src/renderer/src/components/core/Avatar.jsx +2197 -0
  137. package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
  138. package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
  139. package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
  140. package/src/renderer/src/components/core/HoloCard.jsx +64 -0
  141. package/src/renderer/src/components/core/InputBar.jsx +1346 -0
  142. package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
  143. package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
  144. package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
  145. package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
  146. package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
  147. package/src/renderer/src/components/core/native-skills.js +43 -0
  148. package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
  149. package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
  150. package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
  151. package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
  152. package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
  153. package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
  154. package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
  155. package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
  156. package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
  157. package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
  158. package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
  159. package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
  160. package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
  161. package/src/renderer/src/contexts/ChatContext.jsx +61 -0
  162. package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
  163. package/src/renderer/src/data/faqData.js +202 -0
  164. package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
  165. package/src/renderer/src/hooks/agent/index.js +4 -0
  166. package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
  167. package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
  168. package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
  169. package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
  170. package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
  171. package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
  172. package/src/renderer/src/hooks/useAwareness.js +289 -0
  173. package/src/renderer/src/hooks/useChatArchiver.js +52 -0
  174. package/src/renderer/src/hooks/useConfirm.jsx +68 -0
  175. package/src/renderer/src/hooks/useMarkAgent.js +454 -0
  176. package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
  177. package/src/renderer/src/hooks/useVAD.js +511 -0
  178. package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
  179. package/src/renderer/src/main.jsx +12 -0
  180. package/src/renderer/src/pages/ChatStudio.jsx +922 -0
  181. package/src/renderer/src/pages/Configuration.jsx +360 -0
  182. package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
  183. package/src/renderer/src/pages/Guidebook.jsx +917 -0
  184. package/src/renderer/src/pages/Knowledge.jsx +241 -0
  185. package/src/renderer/src/pages/MarkHome.jsx +491 -0
  186. package/src/renderer/src/pages/NeuralCore.jsx +786 -0
  187. package/src/renderer/src/pages/Plugins.jsx +630 -0
  188. package/src/renderer/src/pages/SkillEditor.jsx +380 -0
  189. package/src/renderer/src/pages/Skills.jsx +219 -0
  190. package/src/renderer/src/pages/Subagents.jsx +342 -0
  191. package/src/renderer/src/pages/TelegramBot.jsx +343 -0
  192. package/src/renderer/src/utils/chatContentParser.js +203 -0
  193. package/src/renderer/src/utils/toolClusters.js +201 -0
  194. package/src/server/bin/sha3_wasm_bg.wasm +0 -0
  195. package/src/server/config-manager.js +57 -0
  196. package/src/server/index.js +195 -0
  197. package/src/server/launcher.js +87 -0
  198. package/src/server/memory/db-store.js +758 -0
  199. package/src/server/memory/orama-store.js +177 -0
  200. package/src/server/memory/vector-engine.js +109 -0
  201. package/src/server/routes/ai.routes.js +280 -0
  202. package/src/server/routes/awareness.routes.js +62 -0
  203. package/src/server/routes/chat.routes.js +61 -0
  204. package/src/server/routes/config.routes.js +41 -0
  205. package/src/server/routes/index.js +35 -0
  206. package/src/server/routes/integrations.routes.js +116 -0
  207. package/src/server/routes/memory.routes.js +256 -0
  208. package/src/server/routes/plugins.routes.js +72 -0
  209. package/src/server/routes/skills.routes.js +139 -0
  210. package/src/server/routes/subagents.routes.js +70 -0
  211. package/src/server/routes/tasks.routes.js +175 -0
  212. package/src/server/routes/workspace.routes.js +127 -0
  213. package/src/server/services/ai/ai-utils.js +161 -0
  214. package/src/server/services/ai/openai-provider.js +757 -0
  215. package/src/server/services/ai/web-provider.js +729 -0
  216. package/src/server/services/ai-bridge.js +46 -0
  217. package/src/server/services/deepseek-web.js +956 -0
  218. package/src/server/services/gemini-web.js +170 -0
  219. package/src/server/services/notification-service.js +91 -0
  220. package/src/server/services/updater.js +125 -0
  221. package/src/server/tools/awareness-tracker.js +292 -0
  222. package/src/server/tools/group-tools.js +1164 -0
  223. package/src/server/tools/media-tools.js +244 -0
  224. package/src/server/tools/pc-agent.js +327 -0
  225. package/src/server/tools/screen-service.js +51 -0
  226. package/src/server/ws-hub.js +168 -0
  227. package/src/shared/parsers/mark-tag-parser.js +275 -0
@@ -0,0 +1,375 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import { useYoutubeMusic } from '../contexts/YoutubeMusicContext'
3
+ import {
4
+ Play,
5
+ Pause,
6
+ SkipForward,
7
+ SkipBack,
8
+ Volume,
9
+ Volume2,
10
+ VolumeX,
11
+ ListMusic,
12
+ ChevronDown,
13
+ Music,
14
+ ImageIcon,
15
+ Tv,
16
+ X
17
+ } from 'lucide-react'
18
+
19
+ function formatTime(seconds) {
20
+ if (!seconds || isNaN(seconds)) return '0:00'
21
+ const mins = Math.floor(seconds / 60)
22
+ const secs = Math.floor(seconds % 60)
23
+ return `${mins}:${secs < 10 ? '0' : ''}${secs}`
24
+ }
25
+
26
+ export const YoutubeMusicPlayer = () => {
27
+ const {
28
+ currentTrack,
29
+ queue,
30
+ isPlaying,
31
+ isMuted,
32
+ volume,
33
+ currentTime,
34
+ duration,
35
+ viewMode,
36
+ isPlayerOpen,
37
+ containerId,
38
+ setIsPlayerOpen,
39
+ togglePlayer,
40
+ setViewMode,
41
+ playTrack,
42
+ playPause,
43
+ nextTrack,
44
+ prevTrack,
45
+ seekTo,
46
+ setVolume,
47
+ toggleMute
48
+ } = useYoutubeMusic()
49
+
50
+ const [isSeeking, setIsSeeking] = useState(false)
51
+ const [seekValue, setSeekValue] = useState(0)
52
+ const [showQueue, setShowQueue] = useState(false)
53
+
54
+ // Web Bridge / IPC command synchronization
55
+ const playTrackRef = useRef(playTrack)
56
+ const nextTrackRef = useRef(nextTrack)
57
+ const prevTrackRef = useRef(prevTrack)
58
+ const playPauseRef = useRef(playPause)
59
+
60
+ useEffect(() => {
61
+ playTrackRef.current = playTrack
62
+ nextTrackRef.current = nextTrack
63
+ prevTrackRef.current = prevTrack
64
+ playPauseRef.current = playPause
65
+ }, [playTrack, nextTrack, prevTrack, playPause])
66
+
67
+ useEffect(() => {
68
+ if (window.api?.onExecuteMusicCommand) {
69
+ window.api.onExecuteMusicCommand((command, payload) => {
70
+ if (command === 'play' && payload) {
71
+ if (typeof payload === 'object') {
72
+ playTrackRef.current(payload)
73
+ } else {
74
+ window.api.searchMusic(payload).then((res) => {
75
+ if (res && res.length > 0) {
76
+ playTrackRef.current(res[0], res)
77
+ }
78
+ })
79
+ }
80
+ } else if (command === 'next') nextTrackRef.current()
81
+ else if (command === 'prev') prevTrackRef.current()
82
+ else if (command === 'toggle') playPauseRef.current()
83
+ })
84
+ }
85
+ }, [])
86
+
87
+ const currentSeek = isSeeking ? seekValue : currentTime
88
+ const progressRatio =
89
+ duration > 0 ? Math.min(100, Math.max(0, (currentSeek / duration) * 100)) : 0
90
+ const remainingTime = duration > currentSeek ? duration - currentSeek : 0
91
+
92
+ return (
93
+ <div className="fixed bottom-5 right-5 z-120 flex flex-col items-end gap-2 select-none pointer-events-none">
94
+ {/* Player Main Card */}
95
+ <div
96
+ className={`
97
+ transition-all duration-300 ease-out origin-bottom-right
98
+ ${
99
+ isPlayerOpen
100
+ ? 'opacity-100 scale-100 translate-y-0 pointer-events-auto visible'
101
+ : 'opacity-0 scale-95 translate-y-3 pointer-events-none invisible'
102
+ }
103
+ `}
104
+ style={{ pointerEvents: isPlayerOpen ? 'auto' : 'none' }}
105
+ >
106
+ <div className="relative w-80 sm:w-84 rounded-3xl overflow-hidden shadow-[0_25px_60px_-15px_rgba(0,0,0,0.9)] border border-white/10 bg-[#0d0d0e]/95 backdrop-blur-2xl text-white flex flex-col p-4">
107
+ {/* Subtle Ambient Red Glow Background */}
108
+ {currentTrack.thumbnail && (
109
+ <div
110
+ className="absolute inset-0 bg-cover bg-center blur-3xl opacity-20 pointer-events-none scale-150 transition-opacity duration-700"
111
+ style={{ backgroundImage: `url(${currentTrack.thumbnail})` }}
112
+ />
113
+ )}
114
+
115
+ {/* Top Bar (Dismiss handle & View Mode Toggle) */}
116
+ <div className="relative z-10 flex items-center justify-between pb-3">
117
+ <button
118
+ onClick={() => setIsPlayerOpen(false)}
119
+ className="w-8 h-8 rounded-full flex items-center justify-center text-zinc-400 hover:text-white hover:bg-white/10 active:scale-95 transition-all cursor-pointer"
120
+ title="Tutup Player"
121
+ >
122
+ <ChevronDown size={18} />
123
+ </button>
124
+
125
+ {/* Video vs Cover Pill Switcher */}
126
+ <div className="flex items-center bg-white/10 p-0.5 rounded-full border border-white/5 backdrop-blur-md">
127
+ <button
128
+ onClick={() => setViewMode('thumbnail')}
129
+ className={`px-2.5 py-1 rounded-full text-[11px] font-medium transition-all flex items-center gap-1.5 cursor-pointer ${
130
+ viewMode === 'thumbnail'
131
+ ? 'bg-red-600 text-white shadow-sm'
132
+ : 'text-zinc-400 hover:text-zinc-200'
133
+ }`}
134
+ >
135
+ <ImageIcon size={12} />
136
+ <span>Cover</span>
137
+ </button>
138
+ <button
139
+ onClick={() => setViewMode('video')}
140
+ className={`px-2.5 py-1 rounded-full text-[11px] font-medium transition-all flex items-center gap-1.5 cursor-pointer ${
141
+ viewMode === 'video'
142
+ ? 'bg-red-600 text-white shadow-sm'
143
+ : 'text-zinc-400 hover:text-zinc-200'
144
+ }`}
145
+ >
146
+ <Tv size={12} />
147
+ <span>Video</span>
148
+ </button>
149
+ </div>
150
+
151
+ {/* Queue Toggle Button */}
152
+ <button
153
+ onClick={() => setShowQueue(!showQueue)}
154
+ className={`w-8 h-8 rounded-full flex items-center justify-center transition-all cursor-pointer ${
155
+ showQueue
156
+ ? 'bg-red-600 text-white shadow-sm shadow-red-600/50'
157
+ : 'text-zinc-400 hover:text-white hover:bg-white/10'
158
+ }`}
159
+ title="Daftar Antrean"
160
+ >
161
+ <ListMusic size={17} />
162
+ </button>
163
+ </div>
164
+
165
+ {/* Media Display Screen (Artwork / Single Unified Embedded Video) */}
166
+ <div className="relative z-10 w-full aspect-square rounded-2xl overflow-hidden shadow-2xl shadow-black/80 bg-black/60 border border-white/5 group my-1 flex items-center justify-center">
167
+ {/* SATU-SATUNYA IFRAME PLAYER ASLI (Selalu ter-mount, hanya opacity/z-index yang berganti) */}
168
+ <div
169
+ className={`absolute inset-0 w-full h-full transition-opacity duration-200 ${
170
+ isPlayerOpen && viewMode === 'video'
171
+ ? 'opacity-100 pointer-events-auto z-20'
172
+ : 'opacity-0 pointer-events-none z-0'
173
+ }`}
174
+ >
175
+ <div
176
+ id={containerId}
177
+ className="w-full h-full"
178
+ style={{ pointerEvents: isPlayerOpen && viewMode === 'video' ? 'auto' : 'none' }}
179
+ />
180
+ </div>
181
+
182
+ {/* Cover / Artwork View (Tampil saat viewMode === 'thumbnail') */}
183
+ <div
184
+ className={`absolute inset-0 flex items-center justify-center transition-opacity duration-200 ${
185
+ viewMode === 'thumbnail'
186
+ ? 'opacity-100 pointer-events-auto z-10'
187
+ : 'opacity-0 pointer-events-none z-0'
188
+ }`}
189
+ >
190
+ {currentTrack.thumbnail ? (
191
+ <>
192
+ <img
193
+ src={currentTrack.thumbnail}
194
+ alt={currentTrack.title}
195
+ className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
196
+ />
197
+ <div className="absolute inset-0 bg-linear-to-t from-black/60 via-transparent to-black/20 pointer-events-none" />
198
+ </>
199
+ ) : (
200
+ <div className="flex flex-col items-center gap-2 text-zinc-600">
201
+ <Music size={40} strokeWidth={1.5} />
202
+ <span className="text-xs font-mono">Belum ada lagu</span>
203
+ </div>
204
+ )}
205
+
206
+ {/* Soundwave Animation Badge */}
207
+ {isPlaying && (
208
+ <div className="absolute bottom-3 right-3 flex items-end gap-0.5 px-2 py-1.5 rounded-full bg-black/60 backdrop-blur-md border border-white/10 shadow-lg">
209
+ <span className="w-0.5 bg-red-500 rounded-full animate-[pulse_0.6s_ease-in-out_infinite] h-2.5" />
210
+ <span className="w-0.5 bg-red-500 rounded-full animate-[pulse_0.9s_ease-in-out_infinite] h-4" />
211
+ <span className="w-0.5 bg-red-500 rounded-full animate-[pulse_0.4s_ease-in-out_infinite] h-2" />
212
+ <span className="w-0.5 bg-red-500 rounded-full animate-[pulse_0.7s_ease-in-out_infinite] h-3.5" />
213
+ </div>
214
+ )}
215
+ </div>
216
+ </div>
217
+
218
+ {/* Queue List Overlay Panel */}
219
+ {showQueue && queue && queue.length > 0 && (
220
+ <div className="relative z-20 my-2 max-h-36 overflow-y-auto no-scrollbar rounded-2xl bg-black/80 border border-white/10 p-1.5 flex flex-col gap-0.5 text-xs backdrop-blur-xl">
221
+ {queue.map((item, idx) => {
222
+ const isCurrent = (item.videoId || item.id) === currentTrack.videoId
223
+ return (
224
+ <div
225
+ key={idx}
226
+ onClick={() => playTrack(item)}
227
+ className={`flex items-center justify-between px-3 py-2 rounded-xl cursor-pointer transition-colors ${
228
+ isCurrent
229
+ ? 'bg-red-600/20 text-red-400 font-medium'
230
+ : 'hover:bg-white/5 text-zinc-400 hover:text-zinc-200'
231
+ }`}
232
+ >
233
+ <div className="flex items-center gap-2.5 truncate">
234
+ <span className="text-[11px] text-zinc-500 font-mono w-4">{idx + 1}</span>
235
+ <span className="truncate text-xs">{item.title}</span>
236
+ </div>
237
+ <span className="text-[10px] text-zinc-500 font-mono shrink-0 ml-2">
238
+ {item.timestamp || ''}
239
+ </span>
240
+ </div>
241
+ )
242
+ })}
243
+ </div>
244
+ )}
245
+
246
+ {/* Song Meta (Title & Artist) */}
247
+ <div className="relative z-10 mt-3 mb-2 flex items-center justify-between">
248
+ <div className="flex flex-col min-w-0 pr-2">
249
+ <span
250
+ className="font-semibold text-base text-white truncate tracking-tight"
251
+ title={currentTrack.title}
252
+ >
253
+ {currentTrack.title || 'Tidak ada lagu aktif'}
254
+ </span>
255
+ <span className="text-xs text-zinc-400 truncate mt-0.5" title={currentTrack.artist}>
256
+ {currentTrack.artist || 'Pilih lagu untuk memulai'}
257
+ </span>
258
+ </div>
259
+ </div>
260
+
261
+ {/* Progress Scrubber (Apple Music Style) */}
262
+ <div className="relative z-10 flex flex-col gap-1 my-1">
263
+ <div className="relative flex items-center group">
264
+ <input
265
+ type="range"
266
+ min={0}
267
+ max={duration || 100}
268
+ value={currentSeek}
269
+ onMouseDown={() => setIsSeeking(true)}
270
+ onChange={(e) => setSeekValue(Number(e.target.value))}
271
+ onMouseUp={(e) => {
272
+ setIsSeeking(false)
273
+ seekTo(Number(e.target.value))
274
+ }}
275
+ className="w-full h-1 bg-white/15 rounded-full appearance-none cursor-pointer accent-red-500 focus:outline-none transition-all group-hover:h-1.5"
276
+ style={{
277
+ background: `linear-gradient(to right, #ef4444 ${progressRatio}%, rgba(255, 255, 255, 0.15) ${progressRatio}%)`
278
+ }}
279
+ />
280
+ </div>
281
+ <div className="flex justify-between text-[11px] font-mono text-zinc-400">
282
+ <span>{formatTime(currentSeek)}</span>
283
+ <span>{duration > 0 ? `-${formatTime(remainingTime)}` : '0:00'}</span>
284
+ </div>
285
+ </div>
286
+
287
+ {/* Main Playback Controls Cluster */}
288
+ <div className="relative z-10 flex items-center justify-center gap-6 py-2">
289
+ <button
290
+ onClick={prevTrack}
291
+ className="w-10 h-10 rounded-full flex items-center justify-center text-zinc-300 hover:text-white hover:bg-white/5 active:scale-90 transition-all cursor-pointer"
292
+ title="Sebelumnya"
293
+ >
294
+ <SkipBack size={22} fill="currentColor" />
295
+ </button>
296
+
297
+ <button
298
+ onClick={playPause}
299
+ className="w-13 h-13 rounded-full bg-white text-black flex items-center justify-center hover:scale-105 active:scale-95 transition-all shadow-xl shadow-white/10 cursor-pointer"
300
+ title={isPlaying ? 'Pause' : 'Play'}
301
+ >
302
+ {isPlaying ? (
303
+ <Pause size={22} fill="currentColor" />
304
+ ) : (
305
+ <Play size={22} fill="currentColor" className="ml-0.5" />
306
+ )}
307
+ </button>
308
+
309
+ <button
310
+ onClick={nextTrack}
311
+ className="w-10 h-10 rounded-full flex items-center justify-center text-zinc-300 hover:text-white hover:bg-white/5 active:scale-90 transition-all cursor-pointer"
312
+ title="Berikutnya"
313
+ >
314
+ <SkipForward size={22} fill="currentColor" />
315
+ </button>
316
+ </div>
317
+
318
+ {/* Volume Control Bar */}
319
+ <div className="relative z-10 flex items-center gap-2 px-1 pt-1 pb-0.5">
320
+ <button
321
+ onClick={toggleMute}
322
+ className="text-zinc-400 hover:text-white transition-colors cursor-pointer"
323
+ title={isMuted ? 'Unmute' : 'Mute'}
324
+ >
325
+ {isMuted || volume === 0 ? <VolumeX size={14} /> : <Volume size={14} />}
326
+ </button>
327
+
328
+ <input
329
+ type="range"
330
+ min="0"
331
+ max="100"
332
+ value={isMuted ? 0 : volume}
333
+ onChange={(e) => setVolume(Number(e.target.value))}
334
+ className="w-full h-1 bg-white/15 rounded-full appearance-none cursor-pointer accent-white focus:outline-none"
335
+ style={{
336
+ background: `linear-gradient(to right, rgba(255, 255, 255, 0.8) ${isMuted ? 0 : volume}%, rgba(255, 255, 255, 0.15) ${isMuted ? 0 : volume}%)`
337
+ }}
338
+ />
339
+
340
+ <Volume2 size={14} className="text-zinc-400 shrink-0" />
341
+ </div>
342
+ </div>
343
+ </div>
344
+
345
+ {/* Floating Mini Action Button */}
346
+ <button
347
+ onClick={togglePlayer}
348
+ className={`
349
+ group relative w-10 h-10 rounded-full flex items-center justify-center pointer-events-auto
350
+ shadow-lg shadow-black/80 border border-white/10
351
+ transition-all duration-200 ease-out cursor-pointer
352
+ hover:scale-105 active:scale-95
353
+ ${
354
+ isPlayerOpen
355
+ ? 'bg-white/15 text-white hover:bg-white/20'
356
+ : 'bg-[#121214] text-white hover:border-red-500/40'
357
+ }
358
+ `}
359
+ title={isPlayerOpen ? 'Tutup' : 'YouTube Music'}
360
+ >
361
+ {isPlaying && !isPlayerOpen && (
362
+ <span className="absolute inset-0 rounded-full bg-red-600/30 animate-ping pointer-events-none" />
363
+ )}
364
+
365
+ {isPlayerOpen ? (
366
+ <X size={15} strokeWidth={2} />
367
+ ) : (
368
+ <Music size={16} className={isPlaying ? 'text-red-500 animate-pulse' : 'text-zinc-300'} />
369
+ )}
370
+ </button>
371
+ </div>
372
+ )
373
+ }
374
+
375
+ export default YoutubeMusicPlayer
@@ -0,0 +1,236 @@
1
+ import { useEffect, useRef, useState, useCallback } from 'react'
2
+ import { Camera } from 'lucide-react'
3
+
4
+ export const CameraPreview = ({
5
+ isOpen,
6
+ onCapture,
7
+ onClose,
8
+ deviceId = null,
9
+ countdown = 5,
10
+ isAutonomous = false
11
+ }) => {
12
+ const videoRef = useRef(null)
13
+ const streamRef = useRef(null)
14
+ const canvasRef = useRef(null)
15
+ const [timeLeft, setTimeLeft] = useState(countdown)
16
+ const [error, setError] = useState(null)
17
+ const [isReady, setIsReady] = useState(false)
18
+ const hasCapturedRef = useRef(false)
19
+
20
+ const stopStream = useCallback(() => {
21
+ if (streamRef.current) {
22
+ streamRef.current.getTracks().forEach((track) => track.stop())
23
+ streamRef.current = null
24
+ }
25
+ }, [])
26
+
27
+ const captureFrame = useCallback(() => {
28
+ console.log('[CameraPreview] captureFrame called. hasCaptured:', hasCapturedRef.current, 'video:', !!videoRef.current, 'canvas:', !!canvasRef.current)
29
+ if (hasCapturedRef.current || !videoRef.current || !canvasRef.current) {
30
+ console.log('[CameraPreview] captureFrame aborted!')
31
+ return
32
+ }
33
+ hasCapturedRef.current = true
34
+
35
+ try {
36
+ const video = videoRef.current
37
+ const canvas = canvasRef.current
38
+ const ctx = canvas.getContext('2d')
39
+
40
+ // Maintain aspect ratio, max 1280x720
41
+ let width = video.videoWidth || 1280
42
+ let height = video.videoHeight || 720
43
+
44
+ if (width > 1280 || height > 720) {
45
+ const ratio = Math.min(1280 / width, 720 / height)
46
+ width = Math.floor(width * ratio)
47
+ height = Math.floor(height * ratio)
48
+ }
49
+
50
+ canvas.width = width
51
+ canvas.height = height
52
+ ctx.drawImage(video, 0, 0, width, height)
53
+
54
+ // Convert to base64 JPEG 70%
55
+ const base64Data = canvas.toDataURL('image/jpeg', 0.7)
56
+ console.log('[CameraPreview] Frame captured, size:', Math.round(base64Data.length / 1024), 'KB')
57
+ stopStream()
58
+ onCapture(base64Data)
59
+ } catch (e) {
60
+ console.error('[CameraPreview] Error during captureFrame:', e)
61
+ stopStream()
62
+ onCapture(null)
63
+ }
64
+ }, [onCapture, stopStream])
65
+
66
+ // Main effect: open/close camera
67
+ useEffect(() => {
68
+ if (!isOpen) {
69
+ setIsReady(false)
70
+ return
71
+ }
72
+
73
+ let isMounted = true
74
+ hasCapturedRef.current = false
75
+ setTimeLeft(countdown)
76
+ setError(null)
77
+ setIsReady(false)
78
+
79
+ const initCamera = async () => {
80
+ try {
81
+ console.log('[CameraPreview] Requesting camera access..., deviceId:', deviceId)
82
+
83
+ // Start with simple constraints first, then try specific deviceId
84
+ const constraints = {
85
+ video: deviceId && deviceId !== 'default'
86
+ ? { deviceId: { exact: deviceId } }
87
+ : true,
88
+ audio: false
89
+ }
90
+
91
+ console.log('[CameraPreview] getUserMedia constraints:', JSON.stringify(constraints))
92
+ const stream = await navigator.mediaDevices.getUserMedia(constraints)
93
+ console.log('[CameraPreview] Got stream:', stream.getVideoTracks().length, 'video tracks')
94
+
95
+ if (!isMounted) {
96
+ stream.getTracks().forEach(t => t.stop())
97
+ return
98
+ }
99
+
100
+ streamRef.current = stream
101
+
102
+ if (videoRef.current) {
103
+ videoRef.current.srcObject = stream
104
+
105
+ // Wait for video to actually start playing
106
+ videoRef.current.onloadedmetadata = () => {
107
+ console.log('[CameraPreview] Video metadata loaded, dimensions:', videoRef.current.videoWidth, 'x', videoRef.current.videoHeight)
108
+ if (!isMounted) return
109
+
110
+ videoRef.current.play().then(() => {
111
+ console.log('[CameraPreview] Video playing. isAutonomous prop value:', isAutonomous)
112
+ if (!isMounted) return
113
+ setIsReady(true)
114
+
115
+ if (isAutonomous) {
116
+ // Wait a bit for auto-focus, then snap instantly
117
+ setTimeout(() => {
118
+ console.log('[CameraPreview] setTimeout 800ms finished. isMounted:', isMounted, 'hasCaptured:', hasCapturedRef.current)
119
+ if (isMounted && !hasCapturedRef.current) captureFrame()
120
+ }, 800)
121
+ }
122
+ }).catch(err => {
123
+ console.error('[CameraPreview] Play error:', err)
124
+ })
125
+ }
126
+ }
127
+ } catch (err) {
128
+ console.error('[CameraPreview] Camera access error:', err.name, err.message)
129
+ if (isMounted) {
130
+ setError(err.message)
131
+ stopStream()
132
+ // Delay onClose slightly so error is visible
133
+ setTimeout(() => {
134
+ if (isMounted) onClose()
135
+ }, 2000)
136
+ }
137
+ }
138
+ }
139
+
140
+ initCamera()
141
+
142
+ return () => {
143
+ isMounted = false
144
+ stopStream()
145
+ }
146
+ }, [isOpen]) // Minimal deps - only re-run when isOpen changes
147
+
148
+ // Countdown timer (only when camera is ready and not autonomous)
149
+ useEffect(() => {
150
+ if (!isOpen || !isReady || isAutonomous || error || hasCapturedRef.current) return
151
+
152
+ if (timeLeft <= 0) {
153
+ captureFrame()
154
+ return
155
+ }
156
+
157
+ const timer = setTimeout(() => {
158
+ setTimeLeft(prev => prev - 1)
159
+ }, 1000)
160
+
161
+ return () => clearTimeout(timer)
162
+ }, [isOpen, isReady, isAutonomous, timeLeft, error, captureFrame])
163
+
164
+ // Don't render UI if closed or autonomous mode, but keep video for autonomous
165
+ if (!isOpen) return null
166
+ if (isAutonomous) {
167
+ return (
168
+ <div className="fixed bottom-0 right-0 w-1 h-1 opacity-0 pointer-events-none overflow-hidden">
169
+ <video ref={videoRef} autoPlay playsInline muted className="w-full h-full" />
170
+ <canvas ref={canvasRef} className="w-full h-full" />
171
+ </div>
172
+ )
173
+ }
174
+
175
+ return (
176
+ <div className="fixed bottom-24 right-6 z-50 animate-[slideUpFadeIn_0.3s_ease-out]">
177
+ <div className="w-[320px] rounded-2xl border border-white/10 bg-base-100/70 backdrop-blur-xl shadow-2xl overflow-hidden flex flex-col">
178
+ {/* Header */}
179
+ <div className="px-4 py-3 flex items-center justify-between border-b border-white/5 bg-base-200/50">
180
+ <div className="flex items-center gap-2">
181
+ <Camera className="w-4 h-4 text-primary" />
182
+ <span className="font-semibold text-sm">Mark Glance</span>
183
+ </div>
184
+ {error ? (
185
+ <div className="badge badge-sm border-0 bg-error/20 text-error font-bold">Error</div>
186
+ ) : !isReady ? (
187
+ <span className="loading loading-dots loading-xs text-primary"></span>
188
+ ) : (
189
+ <div className={`badge badge-sm border-0 font-bold ${
190
+ timeLeft > 3 ? 'bg-primary/20 text-primary' :
191
+ timeLeft > 1 ? 'bg-warning/20 text-warning' :
192
+ 'bg-error/20 text-error'
193
+ }`}>
194
+ {timeLeft}s
195
+ </div>
196
+ )}
197
+ </div>
198
+
199
+ {/* Video Container */}
200
+ <div className="relative bg-black w-full aspect-video">
201
+ {error ? (
202
+ <div className="absolute inset-0 flex items-center justify-center text-error/70 text-xs p-4 text-center">
203
+ ⚠️ {error}
204
+ </div>
205
+ ) : (
206
+ <>
207
+ <video
208
+ ref={videoRef}
209
+ autoPlay
210
+ playsInline
211
+ muted
212
+ className="w-full h-full object-cover"
213
+ />
214
+ <canvas ref={canvasRef} className="hidden" />
215
+ </>
216
+ )}
217
+
218
+ {/* Flash Effect on Capture */}
219
+ {timeLeft <= 0 && isReady && !error && (
220
+ <div className="absolute inset-0 bg-white/80 animate-pulse pointer-events-none" />
221
+ )}
222
+ </div>
223
+
224
+ {/* Progress Bar */}
225
+ {isReady && !error && (
226
+ <div className="h-1 w-full bg-base-300/50">
227
+ <div
228
+ className="h-full bg-gradient-to-r from-primary to-secondary transition-all duration-1000 ease-linear"
229
+ style={{ width: `${(timeLeft / countdown) * 100}%` }}
230
+ />
231
+ </div>
232
+ )}
233
+ </div>
234
+ </div>
235
+ )
236
+ }