@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.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- package/src/shared/parsers/mark-tag-parser.js +275 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { MsEdgeTTS, OUTPUT_FORMAT } from 'msedge-tts'
|
|
2
|
+
import { Readable } from 'stream'
|
|
3
|
+
import ytSearch from 'yt-search'
|
|
4
|
+
import { YoutubeTranscript } from 'youtube-transcript-plus'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import os from 'os'
|
|
7
|
+
import fs from 'fs'
|
|
8
|
+
|
|
9
|
+
// Patch MsEdgeTTS prototype to prevent unhandled crashes when streams close prematurely,
|
|
10
|
+
// when connections are rejected, or when WebSocket events throw without handler.
|
|
11
|
+
if (!MsEdgeTTS.__mark_patched) {
|
|
12
|
+
MsEdgeTTS.__mark_patched = true
|
|
13
|
+
|
|
14
|
+
MsEdgeTTS.prototype._pushAudioData = function (data, requestId) {
|
|
15
|
+
if (this._streams && this._streams[requestId] && this._streams[requestId].audio) {
|
|
16
|
+
try {
|
|
17
|
+
if (!this._streams[requestId].audio.destroyed) {
|
|
18
|
+
this._streams[requestId].audio.push(data)
|
|
19
|
+
}
|
|
20
|
+
} catch (_) {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
MsEdgeTTS.prototype._pushMetadata = function (data, requestId) {
|
|
25
|
+
if (this._streams && this._streams[requestId] && this._streams[requestId].metadata) {
|
|
26
|
+
try {
|
|
27
|
+
if (!this._streams[requestId].metadata.destroyed) {
|
|
28
|
+
this._streams[requestId].metadata.push(data)
|
|
29
|
+
}
|
|
30
|
+
} catch (_) {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Override _rawSSMLRequest with explicit catch handler on _send() to eliminate UnhandledPromiseRejection
|
|
35
|
+
MsEdgeTTS.prototype._rawSSMLRequest = function (requestSSML) {
|
|
36
|
+
this._metadataCheck()
|
|
37
|
+
const requestId = MsEdgeTTS.randomHex(16)
|
|
38
|
+
const request =
|
|
39
|
+
`X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n` +
|
|
40
|
+
requestSSML.trim()
|
|
41
|
+
const self = this
|
|
42
|
+
const audioStream = new Readable({
|
|
43
|
+
read() {},
|
|
44
|
+
destroy(error, callback) {
|
|
45
|
+
delete self._streams[requestId]
|
|
46
|
+
if (typeof callback === 'function') callback(error)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
const metadataStream = this._hasMetadataBoundaries()
|
|
50
|
+
? new Readable({
|
|
51
|
+
read() {},
|
|
52
|
+
destroy(error, callback) {
|
|
53
|
+
delete self._streams[requestId]
|
|
54
|
+
if (typeof callback === 'function') callback(error)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
: null
|
|
58
|
+
|
|
59
|
+
audioStream.on('error', () => {
|
|
60
|
+
audioStream.destroy()
|
|
61
|
+
metadataStream?.destroy()
|
|
62
|
+
})
|
|
63
|
+
audioStream.once('close', () => {
|
|
64
|
+
audioStream.destroy()
|
|
65
|
+
metadataStream?.destroy()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
this._streams[requestId] = {
|
|
69
|
+
audio: audioStream,
|
|
70
|
+
metadata: metadataStream
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Attach catch handler directly to eliminate UnhandledPromiseRejection and pipe error to audioStream
|
|
74
|
+
this._send(request).catch((err) => {
|
|
75
|
+
const errMsg = typeof err === 'string' ? err : err?.message || 'TTS connection failed'
|
|
76
|
+
if (!audioStream.destroyed) {
|
|
77
|
+
audioStream.destroy(new Error(errMsg))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return { audioStream, metadataStream, requestId }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const originalInitClient = MsEdgeTTS.prototype._initClient
|
|
85
|
+
MsEdgeTTS.prototype._initClient = async function () {
|
|
86
|
+
try {
|
|
87
|
+
const res = await originalInitClient.apply(this, arguments)
|
|
88
|
+
if (this._ws) {
|
|
89
|
+
const origOnMessage = this._ws.onmessage
|
|
90
|
+
this._ws.onmessage = (m) => {
|
|
91
|
+
try {
|
|
92
|
+
const buffer = Buffer.from(m.data)
|
|
93
|
+
const message = buffer.toString()
|
|
94
|
+
const match = /X-RequestId:(.*?)\r\n/gm.exec(message)
|
|
95
|
+
const requestId = match ? match[1] : null
|
|
96
|
+
|
|
97
|
+
if (message.includes('Path:turn.end') && requestId) {
|
|
98
|
+
if (this._streams && this._streams[requestId] && this._streams[requestId].audio) {
|
|
99
|
+
try {
|
|
100
|
+
if (!this._streams[requestId].audio.destroyed) {
|
|
101
|
+
this._streams[requestId].audio.push(null)
|
|
102
|
+
}
|
|
103
|
+
} catch (_) {}
|
|
104
|
+
}
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (origOnMessage) {
|
|
109
|
+
origOnMessage.call(this._ws, m)
|
|
110
|
+
}
|
|
111
|
+
} catch (_) {
|
|
112
|
+
// Ignore unparseable or orphaned socket packets
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return res
|
|
117
|
+
} catch (connectErr) {
|
|
118
|
+
const msg =
|
|
119
|
+
typeof connectErr === 'string' ? connectErr : connectErr?.message || 'TTS connection failed'
|
|
120
|
+
throw new Error(msg)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const TEMP_DIR = path.join(os.tmpdir(), 'mark-audio')
|
|
126
|
+
if (!fs.existsSync(TEMP_DIR)) {
|
|
127
|
+
fs.mkdirSync(TEMP_DIR, { recursive: true })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const CUTE_ROBOT_PITCH = 55
|
|
131
|
+
export const CUTE_ROBOT_RATE = 10
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Menghasilkan readable stream audio langsung untuk HTTP response streaming.
|
|
135
|
+
* Menggunakan instance terisolasi per permintaan dengan penutupan socket otomatis
|
|
136
|
+
* agar tahan terhadap idle timeout dan permintaan kalimat paralel.
|
|
137
|
+
* Default pitch +55Hz dan rate +10% menghasilkan nada suara robot kecil yang imut & ekspresif.
|
|
138
|
+
* @param {string} text
|
|
139
|
+
* @param {string} [voice='id-ID-ArdiNeural']
|
|
140
|
+
* @param {number|string} [rate=10]
|
|
141
|
+
* @param {number|string} [pitch=55]
|
|
142
|
+
*/
|
|
143
|
+
export async function streamTTS(text, voice = 'id-ID-ArdiNeural', rate = 10, pitch = 55) {
|
|
144
|
+
if (!text || !text.trim()) throw new Error('Teks kosong untuk TTS')
|
|
145
|
+
|
|
146
|
+
let selectedVoice = 'id-ID-ArdiNeural'
|
|
147
|
+
if (typeof voice === 'string' && voice.trim() && !/^-?\d+$/.test(voice.trim())) {
|
|
148
|
+
selectedVoice = voice.trim()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const numRate = typeof rate === 'number' ? rate : (rate !== undefined && rate !== '' ? parseInt(rate, 10) : CUTE_ROBOT_RATE) || CUTE_ROBOT_RATE
|
|
152
|
+
const numPitch = typeof pitch === 'number' ? pitch : (pitch !== undefined && pitch !== '' ? parseInt(pitch, 10) : CUTE_ROBOT_PITCH) || CUTE_ROBOT_PITCH
|
|
153
|
+
const rateStr = numRate >= 0 ? `+${numRate}%` : `${numRate}%`
|
|
154
|
+
const pitchStr = numPitch >= 0 ? `+${numPitch}Hz` : `${numPitch}Hz`
|
|
155
|
+
|
|
156
|
+
const tts = new MsEdgeTTS()
|
|
157
|
+
await tts.setMetadata(selectedVoice, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3)
|
|
158
|
+
const streamObj = tts.toStream(text, { rate: rateStr, pitch: pitchStr })
|
|
159
|
+
|
|
160
|
+
const cleanup = () => {
|
|
161
|
+
try {
|
|
162
|
+
tts.close()
|
|
163
|
+
} catch (_) {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
streamObj.audioStream.once('end', cleanup)
|
|
167
|
+
streamObj.audioStream.once('close', cleanup)
|
|
168
|
+
streamObj.audioStream.once('error', cleanup)
|
|
169
|
+
|
|
170
|
+
return streamObj.audioStream
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Sintesis suara menggunakan Microsoft Edge TTS (Buffer RAM langsung tanpa disk write)
|
|
175
|
+
* @param {string} text
|
|
176
|
+
* @param {string} [voice='id-ID-ArdiNeural']
|
|
177
|
+
* @param {number|string} [rate=10]
|
|
178
|
+
* @param {number|string} [pitch=55]
|
|
179
|
+
* @returns {Promise<{ filePath: string, audioBase64: string }>}
|
|
180
|
+
*/
|
|
181
|
+
export async function synthesizeTTS(text, voice = 'id-ID-ArdiNeural', rate = 10, pitch = 55) {
|
|
182
|
+
if (!text || !text.trim()) throw new Error('Teks kosong untuk TTS')
|
|
183
|
+
|
|
184
|
+
let selectedVoice = 'id-ID-ArdiNeural'
|
|
185
|
+
if (typeof voice === 'string' && voice.trim() && !/^-?\d+$/.test(voice.trim())) {
|
|
186
|
+
selectedVoice = voice.trim()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const audioStream = await streamTTS(text, selectedVoice, rate, pitch)
|
|
190
|
+
const chunks = []
|
|
191
|
+
|
|
192
|
+
await new Promise((resolve, reject) => {
|
|
193
|
+
audioStream.on('data', (chunk) => chunks.push(chunk))
|
|
194
|
+
audioStream.on('end', resolve)
|
|
195
|
+
audioStream.on('error', reject)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const buffer = Buffer.concat(chunks)
|
|
199
|
+
const audioBase64 = `data:audio/mp3;base64,${buffer.toString('base64')}`
|
|
200
|
+
|
|
201
|
+
return { filePath: '', audioBase64 }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Cari video atau musik YouTube
|
|
206
|
+
* @param {string} query
|
|
207
|
+
*/
|
|
208
|
+
export async function searchYoutube(query) {
|
|
209
|
+
if (!query) return []
|
|
210
|
+
const result = await ytSearch(query)
|
|
211
|
+
return result.videos.slice(0, 10).map((v) => ({
|
|
212
|
+
id: v.videoId,
|
|
213
|
+
videoId: v.videoId,
|
|
214
|
+
title: v.title,
|
|
215
|
+
url: v.url,
|
|
216
|
+
timestamp: v.timestamp,
|
|
217
|
+
seconds: v.seconds,
|
|
218
|
+
views: v.views,
|
|
219
|
+
author: v.author?.name || 'Unknown Artist',
|
|
220
|
+
artist: v.author?.name || 'Unknown Artist',
|
|
221
|
+
thumbnail: v.thumbnail || v.image || `https://img.youtube.com/vi/${v.videoId}/hqdefault.jpg`
|
|
222
|
+
}))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Mengambil transkrip dari video YouTube
|
|
227
|
+
* @param {string} url
|
|
228
|
+
* @param {string} [lang='id']
|
|
229
|
+
*/
|
|
230
|
+
export async function getTranscript(url, lang = 'id') {
|
|
231
|
+
if (!url || typeof url !== 'string') return ''
|
|
232
|
+
try {
|
|
233
|
+
const transcriptList = await YoutubeTranscript.fetchTranscript(url, { lang })
|
|
234
|
+
if (transcriptList && transcriptList.length > 0) {
|
|
235
|
+
return transcriptList.map((t) => t.text).join(' ')
|
|
236
|
+
}
|
|
237
|
+
// Fallback tanpa filter bahasa jika bahasa target tidak tersedia
|
|
238
|
+
const fallbackList = await YoutubeTranscript.fetchTranscript(url)
|
|
239
|
+
return fallbackList.map((t) => t.text).join(' ')
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.warn('[YouTube Transcript] Gagal mengambil transkrip:', err?.message || err)
|
|
242
|
+
return `Gagal mengambil transkrip video: ${err?.message || 'Video tidak memiliki transkrip atau dibatasi'}`
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { spawn } from 'child_process'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { wsHub } from '../ws-hub.js'
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
const __dirname = path.dirname(__filename)
|
|
9
|
+
|
|
10
|
+
let daemonProcess = null
|
|
11
|
+
let daemonReady = false
|
|
12
|
+
let pendingResolve = null
|
|
13
|
+
let daemonBuffer = ''
|
|
14
|
+
let lastReadResult = null
|
|
15
|
+
let lastReadTimestamp = 0
|
|
16
|
+
let stateChanged = false
|
|
17
|
+
const CACHE_TTL = 10000
|
|
18
|
+
|
|
19
|
+
let overlayProcess = null
|
|
20
|
+
let overlayReady = false
|
|
21
|
+
|
|
22
|
+
function getScriptPath(scriptName) {
|
|
23
|
+
const candidates = [
|
|
24
|
+
path.resolve(__dirname, `../../../src/main/pc-agent-scripts/${scriptName}`),
|
|
25
|
+
path.resolve(__dirname, `../../main/pc-agent-scripts/${scriptName}`),
|
|
26
|
+
path.resolve(__dirname, `../../../resources/scripts/${scriptName}`),
|
|
27
|
+
path.resolve(__dirname, `../../resources/scripts/${scriptName}`)
|
|
28
|
+
]
|
|
29
|
+
for (const c of candidates) {
|
|
30
|
+
if (fs.existsSync(c)) return c
|
|
31
|
+
}
|
|
32
|
+
return candidates[0]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getDaemonScriptPath() {
|
|
36
|
+
return getScriptPath('pc-daemon.ps1')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getOverlayScriptPath() {
|
|
40
|
+
return getScriptPath('pc-overlay.ps1')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isOverlayAlive() {
|
|
44
|
+
return overlayProcess && !overlayProcess.killed
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function startOverlay() {
|
|
48
|
+
if (isOverlayAlive()) return
|
|
49
|
+
|
|
50
|
+
const scriptPath = getOverlayScriptPath()
|
|
51
|
+
if (!fs.existsSync(scriptPath)) {
|
|
52
|
+
console.warn('[PC-Agent] Skrip overlay tidak ditemukan:', scriptPath)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
overlayProcess = spawn('powershell.exe', [
|
|
58
|
+
'-NoProfile',
|
|
59
|
+
'-STA',
|
|
60
|
+
'-ExecutionPolicy',
|
|
61
|
+
'Bypass',
|
|
62
|
+
'-File',
|
|
63
|
+
scriptPath
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
overlayReady = false
|
|
67
|
+
|
|
68
|
+
overlayProcess.stdout.on('data', (chunk) => {
|
|
69
|
+
const text = chunk.toString().trim()
|
|
70
|
+
if (text.includes('READY')) {
|
|
71
|
+
overlayReady = true
|
|
72
|
+
}
|
|
73
|
+
if (text.includes('"event":"abort"') || text.includes('{"event":"abort"}')) {
|
|
74
|
+
console.warn('[PC-Agent] Sinyal ABORT diterima dari Overlay (Ctrl+Shift+S / Tombol Batal)!')
|
|
75
|
+
// Hentikan sesi dan picu abort di WebSocket hub
|
|
76
|
+
stopDaemon()
|
|
77
|
+
stopOverlay()
|
|
78
|
+
wsHub.broadcast('ai:abort', { source: 'pc-overlay', reason: 'User pressed Ctrl+Shift+S or clicked Cancel' })
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
overlayProcess.stderr.on('data', (chunk) => {
|
|
83
|
+
console.warn('[PC-Agent Overlay Error]:', chunk.toString())
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
overlayProcess.on('close', () => {
|
|
87
|
+
overlayProcess = null
|
|
88
|
+
overlayReady = false
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
overlayProcess.on('error', (err) => {
|
|
92
|
+
console.error('[PC-Agent] Gagal spawn overlay:', err)
|
|
93
|
+
overlayProcess = null
|
|
94
|
+
overlayReady = false
|
|
95
|
+
})
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error('[PC-Agent] Error menjalankan overlay:', err)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function stopOverlay() {
|
|
102
|
+
if (overlayProcess && !overlayProcess.killed) {
|
|
103
|
+
try {
|
|
104
|
+
overlayProcess.kill()
|
|
105
|
+
} catch (_) {}
|
|
106
|
+
overlayProcess = null
|
|
107
|
+
overlayReady = false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isDaemonAlive() {
|
|
112
|
+
return daemonProcess && !daemonProcess.killed && daemonReady
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function startDaemon() {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
if (isDaemonAlive()) {
|
|
118
|
+
resolve()
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const scriptPath = getDaemonScriptPath()
|
|
123
|
+
|
|
124
|
+
daemonProcess = spawn('powershell.exe', [
|
|
125
|
+
'-NoProfile',
|
|
126
|
+
'-WindowStyle',
|
|
127
|
+
'Hidden',
|
|
128
|
+
'-ExecutionPolicy',
|
|
129
|
+
'Bypass',
|
|
130
|
+
'-File',
|
|
131
|
+
scriptPath
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
daemonBuffer = ''
|
|
135
|
+
daemonReady = false
|
|
136
|
+
|
|
137
|
+
daemonProcess.stdout.on('data', (chunk) => {
|
|
138
|
+
daemonBuffer += chunk.toString()
|
|
139
|
+
|
|
140
|
+
const delimiterIndex = daemonBuffer.indexOf('---MARK_DONE---')
|
|
141
|
+
if (delimiterIndex !== -1) {
|
|
142
|
+
const response = daemonBuffer.substring(0, delimiterIndex).trim()
|
|
143
|
+
daemonBuffer = daemonBuffer.substring(delimiterIndex + '---MARK_DONE---'.length).trimStart()
|
|
144
|
+
|
|
145
|
+
if (!daemonReady) {
|
|
146
|
+
daemonReady = true
|
|
147
|
+
resolve()
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (pendingResolve) {
|
|
152
|
+
const resolveFn = pendingResolve
|
|
153
|
+
pendingResolve = null
|
|
154
|
+
resolveFn(response)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
daemonProcess.stderr.on('data', () => {})
|
|
160
|
+
|
|
161
|
+
daemonProcess.on('close', () => {
|
|
162
|
+
daemonProcess = null
|
|
163
|
+
daemonReady = false
|
|
164
|
+
if (pendingResolve) {
|
|
165
|
+
const resolveFn = pendingResolve
|
|
166
|
+
pendingResolve = null
|
|
167
|
+
resolveFn('')
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
daemonProcess.on('error', (err) => {
|
|
172
|
+
console.error('[PC-Agent] Gagal spawn daemon:', err)
|
|
173
|
+
daemonProcess = null
|
|
174
|
+
reject(err)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (!daemonReady) {
|
|
179
|
+
console.warn('[PC-Agent] Timeout menunggu startup daemon (15s)')
|
|
180
|
+
resolve()
|
|
181
|
+
}
|
|
182
|
+
}, 15000)
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function stopDaemon() {
|
|
187
|
+
if (daemonProcess && !daemonProcess.killed) {
|
|
188
|
+
try {
|
|
189
|
+
daemonProcess.stdin.write(JSON.stringify({ cmd: 'exit' }) + '\n')
|
|
190
|
+
} catch (_) {}
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
if (daemonProcess && !daemonProcess.killed) {
|
|
193
|
+
try {
|
|
194
|
+
daemonProcess.kill()
|
|
195
|
+
} catch (_) {}
|
|
196
|
+
}
|
|
197
|
+
daemonProcess = null
|
|
198
|
+
daemonReady = false
|
|
199
|
+
}, 1000)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function sendCommand(cmd) {
|
|
204
|
+
return new Promise((resolve) => {
|
|
205
|
+
if (!isDaemonAlive()) {
|
|
206
|
+
resolve(JSON.stringify({ status: 'error', message: 'Daemon Win32 belum berjalan' }))
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
pendingResolve = resolve
|
|
211
|
+
try {
|
|
212
|
+
daemonProcess.stdin.write(JSON.stringify(cmd) + '\n')
|
|
213
|
+
} catch (err) {
|
|
214
|
+
pendingResolve = null
|
|
215
|
+
resolve(JSON.stringify({ status: 'error', message: 'Gagal kirim ke daemon: ' + err.message }))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
if (pendingResolve === resolve) {
|
|
220
|
+
pendingResolve = null
|
|
221
|
+
resolve(JSON.stringify({ status: 'error', message: 'Command timeout (30s)' }))
|
|
222
|
+
}
|
|
223
|
+
}, 30000)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function readDesktop(options = {}) {
|
|
228
|
+
const maxElements = options.maxElements || 60
|
|
229
|
+
if (!stateChanged && lastReadResult && Date.now() - lastReadTimestamp < CACHE_TTL) {
|
|
230
|
+
return { ...lastReadResult, method: 'cached' }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!isDaemonAlive()) {
|
|
234
|
+
await startDaemon()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const raw = await sendCommand({ cmd: 'read-ui', maxElements, roles: options.roles })
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(raw)
|
|
240
|
+
lastReadResult = parsed
|
|
241
|
+
lastReadTimestamp = Date.now()
|
|
242
|
+
stateChanged = false
|
|
243
|
+
return parsed
|
|
244
|
+
} catch (_) {
|
|
245
|
+
return { status: 'error', message: raw }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function executeClick(x, y) {
|
|
250
|
+
startOverlay()
|
|
251
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
252
|
+
stateChanged = true
|
|
253
|
+
const raw = await sendCommand({ cmd: 'click', x, y })
|
|
254
|
+
return raw
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function executeType(text) {
|
|
258
|
+
startOverlay()
|
|
259
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
260
|
+
stateChanged = true
|
|
261
|
+
const raw = await sendCommand({ cmd: 'type', text })
|
|
262
|
+
return raw
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function executeKey(combo) {
|
|
266
|
+
startOverlay()
|
|
267
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
268
|
+
stateChanged = true
|
|
269
|
+
const raw = await sendCommand({ cmd: 'key', combo })
|
|
270
|
+
return raw
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function executeScroll(direction = 'down', amount = 3) {
|
|
274
|
+
startOverlay()
|
|
275
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
276
|
+
stateChanged = true
|
|
277
|
+
const raw = await sendCommand({ cmd: 'scroll', direction, amount })
|
|
278
|
+
return raw
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function openApp(target) {
|
|
282
|
+
startOverlay()
|
|
283
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
284
|
+
stateChanged = true
|
|
285
|
+
const raw = await sendCommand({ cmd: 'open', target })
|
|
286
|
+
return raw
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function listWindows() {
|
|
290
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
291
|
+
const raw = await sendCommand({ cmd: 'list-windows' })
|
|
292
|
+
try {
|
|
293
|
+
return JSON.parse(raw)
|
|
294
|
+
} catch (_) {
|
|
295
|
+
return []
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function focusWindow(title) {
|
|
300
|
+
startOverlay()
|
|
301
|
+
if (!isDaemonAlive()) await startDaemon()
|
|
302
|
+
stateChanged = true
|
|
303
|
+
const raw = await sendCommand({ cmd: 'focus-window', title })
|
|
304
|
+
return raw
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export async function executeDoubleClick(x, y) {
|
|
308
|
+
await executeClick(x, y)
|
|
309
|
+
await new Promise((r) => setTimeout(r, 60))
|
|
310
|
+
return await executeClick(x, y)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function openPCSession() {
|
|
314
|
+
startOverlay()
|
|
315
|
+
await startDaemon()
|
|
316
|
+
return { success: true }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function closePCSession() {
|
|
320
|
+
stopOverlay()
|
|
321
|
+
stopDaemon()
|
|
322
|
+
return { success: true }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function isPCSessionOpen() {
|
|
326
|
+
return isDaemonAlive()
|
|
327
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { exec } from 'child_process'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import util from 'util'
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
|
|
7
|
+
const execPromise = util.promisify(exec)
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
export async function captureDesktopScreenshotsBase64() {
|
|
12
|
+
if (process.platform !== 'win32') {
|
|
13
|
+
throw new Error('Screen capture hanya didukung di lingkungan Windows.')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const scriptPath = path.resolve(__dirname, '../../main/pc-agent-scripts/take-screenshot.ps1')
|
|
18
|
+
if (!fs.existsSync(scriptPath)) {
|
|
19
|
+
throw new Error(`Skrip screenshot tidak ditemukan di ${scriptPath}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { stdout, stderr } = await execPromise(
|
|
23
|
+
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${scriptPath}"`,
|
|
24
|
+
{ maxBuffer: 50 * 1024 * 1024 }
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const rawOutput = (stdout || '').trim()
|
|
28
|
+
if (!rawOutput) {
|
|
29
|
+
throw new Error(stderr || 'Output screenshot kosong.')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const base64List = rawOutput
|
|
33
|
+
.split('|||')
|
|
34
|
+
.map((str) => str.trim())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
|
|
37
|
+
if (base64List.length === 0) {
|
|
38
|
+
throw new Error('Tidak ada gambar layar yang berhasil diambil.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return base64List.map((b64) => `data:image/jpeg;base64,${b64}`)
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('[Screen Capture Error]:', err)
|
|
44
|
+
throw new Error(`Gagal mengambil screenshot: ${err.message}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function captureDesktopScreenshotBase64() {
|
|
49
|
+
const all = await captureDesktopScreenshotsBase64()
|
|
50
|
+
return all[0]
|
|
51
|
+
}
|