@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,922 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import { useChat } from '../contexts/ChatContext'
|
|
4
|
+
import {
|
|
5
|
+
MessageSquare,
|
|
6
|
+
Plus,
|
|
7
|
+
Trash2,
|
|
8
|
+
Edit2,
|
|
9
|
+
Search,
|
|
10
|
+
Pin,
|
|
11
|
+
X,
|
|
12
|
+
Maximize2,
|
|
13
|
+
Minimize2,
|
|
14
|
+
Sparkles,
|
|
15
|
+
Check,
|
|
16
|
+
RotateCcw,
|
|
17
|
+
Send,
|
|
18
|
+
Loader2,
|
|
19
|
+
ArrowLeft,
|
|
20
|
+
Bot,
|
|
21
|
+
Folder
|
|
22
|
+
} from 'lucide-react'
|
|
23
|
+
import {
|
|
24
|
+
db,
|
|
25
|
+
getAllSessions,
|
|
26
|
+
getSession,
|
|
27
|
+
createSession,
|
|
28
|
+
saveSession,
|
|
29
|
+
deleteSession,
|
|
30
|
+
renameSession,
|
|
31
|
+
getChatData,
|
|
32
|
+
setSessionWorkspace,
|
|
33
|
+
getSessionCompact
|
|
34
|
+
} from '../api/db'
|
|
35
|
+
import ChatList from '../components/ChatList'
|
|
36
|
+
import InputBar from '../components/core/InputBar'
|
|
37
|
+
import { useConfirm } from '../hooks/useConfirm'
|
|
38
|
+
import { stripMarkTags } from '@shared/parsers/mark-tag-parser.js'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Ekstraksi dan filter semua prefix/meta-tag (seperti <mic>, <mood:value>)
|
|
42
|
+
* serta fallback riwayat lama ((Mikrofon), [mood:...]) agar pesan tampil bersih di ChatStudio.
|
|
43
|
+
*
|
|
44
|
+
* @param {string|Array} content
|
|
45
|
+
* @returns {{ cleanContent: string|Array, prefixes: string[] }}
|
|
46
|
+
*/
|
|
47
|
+
export function filterMessagePrefixes(content) {
|
|
48
|
+
if (!content) return { cleanContent: '', prefixes: [] }
|
|
49
|
+
if (typeof content !== 'string') {
|
|
50
|
+
if (Array.isArray(content)) {
|
|
51
|
+
const allPrefixes = []
|
|
52
|
+
const cleanArray = content.map((item) => {
|
|
53
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
54
|
+
const res = filterMessagePrefixes(item.text)
|
|
55
|
+
allPrefixes.push(...res.prefixes)
|
|
56
|
+
return { ...item, text: res.cleanContent }
|
|
57
|
+
}
|
|
58
|
+
return item
|
|
59
|
+
})
|
|
60
|
+
return { cleanContent: cleanArray, prefixes: allPrefixes }
|
|
61
|
+
}
|
|
62
|
+
return { cleanContent: content, prefixes: [] }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const prefixes = []
|
|
66
|
+
const cleanContent = stripMarkTags(content)
|
|
67
|
+
.replace(/(?:<mic>|\(Mikrofon\))/gi, (match) => {
|
|
68
|
+
prefixes.push(match)
|
|
69
|
+
return ''
|
|
70
|
+
})
|
|
71
|
+
.trim()
|
|
72
|
+
|
|
73
|
+
return { cleanContent, prefixes }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const ChatStudio = ({ isOpen, onClose, chatContext: propChatContext }) => {
|
|
77
|
+
const navigate = useNavigate()
|
|
78
|
+
const contextChat = useChat()
|
|
79
|
+
const chatContext = propChatContext || contextChat
|
|
80
|
+
const {
|
|
81
|
+
chatData: mainChatData,
|
|
82
|
+
setChatData: setMainChatData,
|
|
83
|
+
handlePlanningCommand,
|
|
84
|
+
handleIntervention,
|
|
85
|
+
isLoading: isMainLoading,
|
|
86
|
+
isAgentBusy,
|
|
87
|
+
runningSessionId,
|
|
88
|
+
runningSessionIds = [],
|
|
89
|
+
handleStop,
|
|
90
|
+
isRecording,
|
|
91
|
+
isProcessing,
|
|
92
|
+
audioIntensity,
|
|
93
|
+
startRecording,
|
|
94
|
+
stopRecording,
|
|
95
|
+
inputSource,
|
|
96
|
+
setCurrentActiveSessionId
|
|
97
|
+
} = chatContext || {}
|
|
98
|
+
|
|
99
|
+
const [sessions, setSessions] = useState([])
|
|
100
|
+
const [activeSessionId, setActiveSessionId] = useState('1')
|
|
101
|
+
const [lastCompactedMessageId, setLastCompactedMessageId] = useState(null)
|
|
102
|
+
const [activeSessionCompact, setActiveSessionCompact] = useState(null)
|
|
103
|
+
const [loading, setLoading] = useState(true)
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (typeof setCurrentActiveSessionId === 'function') {
|
|
107
|
+
setCurrentActiveSessionId(String(activeSessionId))
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
if (typeof setCurrentActiveSessionId === 'function') {
|
|
111
|
+
setCurrentActiveSessionId('1')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, [activeSessionId, setCurrentActiveSessionId])
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
let isMounted = true
|
|
118
|
+
const loadCompactData = async () => {
|
|
119
|
+
try {
|
|
120
|
+
const data = await getSessionCompact(String(activeSessionId))
|
|
121
|
+
if (isMounted) {
|
|
122
|
+
setActiveSessionCompact(data || null)
|
|
123
|
+
setLastCompactedMessageId(
|
|
124
|
+
data?.lastCompactedMessageId || data?.last_compacted_message_id || null
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
} catch (_) {}
|
|
128
|
+
}
|
|
129
|
+
loadCompactData()
|
|
130
|
+
|
|
131
|
+
const handleCompactUpdated = (e) => {
|
|
132
|
+
if (String(e.detail?.sessionId) === String(activeSessionId)) {
|
|
133
|
+
setLastCompactedMessageId(e.detail?.lastCompactedMessageId || null)
|
|
134
|
+
setActiveSessionCompact({
|
|
135
|
+
summaryBlock: e.detail?.summaryBlock,
|
|
136
|
+
lastCompactedMessageId: e.detail?.lastCompactedMessageId
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
window.addEventListener('session-compact-updated', handleCompactUpdated)
|
|
142
|
+
return () => {
|
|
143
|
+
isMounted = false
|
|
144
|
+
window.removeEventListener('session-compact-updated', handleCompactUpdated)
|
|
145
|
+
}
|
|
146
|
+
}, [activeSessionId])
|
|
147
|
+
|
|
148
|
+
const [activeSessionData, setActiveSessionData] = useState([])
|
|
149
|
+
const [visibleMessageCount, setVisibleMessageCount] = useState(40)
|
|
150
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
151
|
+
const [editingSessionId, setEditingSessionId] = useState(null)
|
|
152
|
+
const [editingTitle, setEditingTitle] = useState('')
|
|
153
|
+
|
|
154
|
+
const messagesContainerRef = useRef(null)
|
|
155
|
+
const messagesEndRef = useRef(null)
|
|
156
|
+
const { confirm, ModalComponent } = useConfirm()
|
|
157
|
+
|
|
158
|
+
const loadAllSessions = async () => {
|
|
159
|
+
try {
|
|
160
|
+
const list = await getAllSessions()
|
|
161
|
+
setSessions(list || [])
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error('Error loading sessions:', e)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
let isMounted = true
|
|
169
|
+
const initStudio = async () => {
|
|
170
|
+
try {
|
|
171
|
+
setLoading(true)
|
|
172
|
+
await loadAllSessions()
|
|
173
|
+
} catch (err) {
|
|
174
|
+
console.error('Error loading sessions:', err)
|
|
175
|
+
} finally {
|
|
176
|
+
if (isMounted) setLoading(false)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
initStudio()
|
|
180
|
+
return () => {
|
|
181
|
+
isMounted = false
|
|
182
|
+
}
|
|
183
|
+
}, [])
|
|
184
|
+
|
|
185
|
+
// Direct display pipeline: Main Thread uses mainChatData directly with 0ms lag
|
|
186
|
+
const currentDisplayMessages =
|
|
187
|
+
String(activeSessionId) === '1' ? mainChatData || [] : activeSessionData
|
|
188
|
+
|
|
189
|
+
const isCurrentLoading =
|
|
190
|
+
runningSessionIds.map(String).includes(String(activeSessionId)) ||
|
|
191
|
+
(String(activeSessionId) === '1' && !runningSessionIds.length && (isMainLoading || isAgentBusy))
|
|
192
|
+
|
|
193
|
+
// Sync active session data for custom sessions (id > 1)
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
setVisibleMessageCount(30)
|
|
196
|
+
if (String(activeSessionId) === '1') return
|
|
197
|
+
let isCancelled = false
|
|
198
|
+
getChatData(activeSessionId).then((data) => {
|
|
199
|
+
if (!isCancelled) {
|
|
200
|
+
setActiveSessionData(data || [])
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
return () => {
|
|
204
|
+
isCancelled = true
|
|
205
|
+
}
|
|
206
|
+
}, [activeSessionId])
|
|
207
|
+
|
|
208
|
+
// Sinkronisasi status karakter gauge ke UI saat sesi aktif dimuat/berganti
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!currentDisplayMessages || currentDisplayMessages.length === 0) {
|
|
211
|
+
window.dispatchEvent(
|
|
212
|
+
new CustomEvent('context-tracker-updated', {
|
|
213
|
+
detail: {
|
|
214
|
+
sessionId: String(activeSessionId),
|
|
215
|
+
currentTokens: 0,
|
|
216
|
+
maxTokens: 256000,
|
|
217
|
+
currentChars: 0,
|
|
218
|
+
maxChars: 256000,
|
|
219
|
+
percentage: 0
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let isMounted = true
|
|
227
|
+
import('../api/ai/contextManager')
|
|
228
|
+
.then(({ calculateSessionTokens, MAX_CONTEXT_TOKENS }) => {
|
|
229
|
+
if (!isMounted) return
|
|
230
|
+
const tokens = calculateSessionTokens(
|
|
231
|
+
currentDisplayMessages,
|
|
232
|
+
activeSessionCompact?.summaryBlock || activeSessionCompact?.summary_block || '',
|
|
233
|
+
activeSessionCompact?.lastCompactedMessageId ||
|
|
234
|
+
activeSessionCompact?.last_compacted_message_id ||
|
|
235
|
+
null,
|
|
236
|
+
'',
|
|
237
|
+
activeSessionCompact?.lastCompactedAt || activeSessionCompact?.last_compacted_at || null
|
|
238
|
+
)
|
|
239
|
+
window.dispatchEvent(
|
|
240
|
+
new CustomEvent('context-tracker-updated', {
|
|
241
|
+
detail: {
|
|
242
|
+
sessionId: String(activeSessionId),
|
|
243
|
+
currentTokens: tokens,
|
|
244
|
+
maxTokens: MAX_CONTEXT_TOKENS,
|
|
245
|
+
percentage: Math.min(100, (tokens / MAX_CONTEXT_TOKENS) * 100),
|
|
246
|
+
currentChars: tokens,
|
|
247
|
+
maxChars: MAX_CONTEXT_TOKENS
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
)
|
|
251
|
+
})
|
|
252
|
+
.catch(() => {})
|
|
253
|
+
|
|
254
|
+
return () => {
|
|
255
|
+
isMounted = false
|
|
256
|
+
}
|
|
257
|
+
}, [activeSessionId, currentDisplayMessages?.length, activeSessionCompact])
|
|
258
|
+
|
|
259
|
+
const handleSendMessage = async (prompt, sendOptions = {}) => {
|
|
260
|
+
if (!prompt.trim()) return
|
|
261
|
+
|
|
262
|
+
const rawDisplay = sendOptions?.displayPrompt || prompt
|
|
263
|
+
// Auto-update session title if it's default
|
|
264
|
+
const currentSession = sessions.find((s) => String(s.id) === String(activeSessionId))
|
|
265
|
+
let newTitle = currentSession?.title
|
|
266
|
+
if (newTitle === 'Percakapan Baru' && rawDisplay.length > 0) {
|
|
267
|
+
newTitle = rawDisplay.slice(0, 30) + (rawDisplay.length > 30 ? '...' : '')
|
|
268
|
+
await renameSession(activeSessionId, newTitle)
|
|
269
|
+
await loadAllSessions()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (isCurrentLoading && typeof handleIntervention === 'function') {
|
|
273
|
+
handleIntervention(prompt, activeSessionId, { displayPrompt: rawDisplay })
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const isMain = String(activeSessionId) === '1' || String(activeSessionId) === '1.0'
|
|
278
|
+
const commandOpts = {
|
|
279
|
+
workspaceRoot: currentSession?.workspaceRoot || null,
|
|
280
|
+
displayPrompt: rawDisplay,
|
|
281
|
+
attachedFiles: sendOptions?.attachedFiles,
|
|
282
|
+
...(!isMain ? { sessionId: activeSessionId, customChatData: activeSessionData } : {})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
handlePlanningCommand(prompt, false, false, commandOpts)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Real-time live background sync across sessions & trigger quick prompt
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
const handleSessionUpdate = (e) => {
|
|
291
|
+
if (e.detail && String(e.detail.sessionId) === String(activeSessionId)) {
|
|
292
|
+
setActiveSessionData(e.detail.data || [])
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const handleQuickPrompt = (e) => {
|
|
297
|
+
if (e.detail?.prompt) {
|
|
298
|
+
handleSendMessage(e.detail.prompt)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
window.addEventListener('session-updated', handleSessionUpdate)
|
|
303
|
+
window.addEventListener('trigger-quick-prompt', handleQuickPrompt)
|
|
304
|
+
|
|
305
|
+
return () => {
|
|
306
|
+
window.removeEventListener('session-updated', handleSessionUpdate)
|
|
307
|
+
window.removeEventListener('trigger-quick-prompt', handleQuickPrompt)
|
|
308
|
+
}
|
|
309
|
+
}, [activeSessionId, handleSendMessage])
|
|
310
|
+
|
|
311
|
+
const lastMessage = currentDisplayMessages[currentDisplayMessages.length - 1]
|
|
312
|
+
const lastMessageContent = lastMessage?.content || ''
|
|
313
|
+
const lastMessageIsThinking = !!lastMessage?.isThinking
|
|
314
|
+
const isAutoScrollEnabledRef = useRef(true)
|
|
315
|
+
|
|
316
|
+
const handleScroll = () => {
|
|
317
|
+
const container = messagesContainerRef.current
|
|
318
|
+
if (!container) return
|
|
319
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
320
|
+
// User is considered at bottom if within 80px
|
|
321
|
+
isAutoScrollEnabledRef.current = scrollHeight - scrollTop - clientHeight < 80
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const scrollToBottom = (behavior = 'auto') => {
|
|
325
|
+
if (messagesContainerRef.current) {
|
|
326
|
+
messagesContainerRef.current.scrollTo({
|
|
327
|
+
top: messagesContainerRef.current.scrollHeight,
|
|
328
|
+
behavior
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Auto scroll to bottom on session change
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
isAutoScrollEnabledRef.current = true
|
|
336
|
+
scrollToBottom('auto')
|
|
337
|
+
}, [activeSessionId])
|
|
338
|
+
|
|
339
|
+
// Direct stick-to-bottom without conflicting timers or layout thrashing
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (isAutoScrollEnabledRef.current && messagesContainerRef.current) {
|
|
342
|
+
messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight
|
|
343
|
+
}
|
|
344
|
+
}, [currentDisplayMessages.length, lastMessageContent, lastMessageIsThinking, isCurrentLoading])
|
|
345
|
+
|
|
346
|
+
const handleCreateNewChat = async () => {
|
|
347
|
+
try {
|
|
348
|
+
window.api?.resetAiSession?.().catch(() => {})
|
|
349
|
+
const newSession = await createSession('Percakapan Baru', [])
|
|
350
|
+
await loadAllSessions()
|
|
351
|
+
setActiveSessionId(newSession.id)
|
|
352
|
+
setActiveSessionData([])
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error('Failed to create session:', err)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const handleDeleteSessionClick = async (e, id) => {
|
|
359
|
+
e.stopPropagation()
|
|
360
|
+
if (id === 1) return
|
|
361
|
+
|
|
362
|
+
const confirmed = await confirm({
|
|
363
|
+
title: 'Hapus Sesi Obrolan',
|
|
364
|
+
message: 'Apakah kamu yakin ingin menghapus sesi percakapan ini secara permanen?',
|
|
365
|
+
confirmText: 'Hapus',
|
|
366
|
+
confirmColor: 'btn-error'
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
if (confirmed?.isConfirmed) {
|
|
370
|
+
await deleteSession(id)
|
|
371
|
+
await loadAllSessions()
|
|
372
|
+
if (String(activeSessionId) === String(id)) {
|
|
373
|
+
setActiveSessionId('1')
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const handleStartRename = (e, session) => {
|
|
379
|
+
e.stopPropagation()
|
|
380
|
+
setEditingSessionId(session.id)
|
|
381
|
+
setEditingTitle(session.title)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const handleSaveRename = async (id) => {
|
|
385
|
+
if (editingTitle.trim()) {
|
|
386
|
+
await renameSession(id, editingTitle.trim())
|
|
387
|
+
await loadAllSessions()
|
|
388
|
+
}
|
|
389
|
+
setEditingSessionId(null)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const handleSelectSessionWorkspace = async () => {
|
|
393
|
+
if (window.api && window.api.selectDirectory) {
|
|
394
|
+
const selected = await window.api.selectDirectory()
|
|
395
|
+
if (selected) {
|
|
396
|
+
await setSessionWorkspace(activeSessionId, selected)
|
|
397
|
+
setSessions((prev) =>
|
|
398
|
+
prev.map((s) =>
|
|
399
|
+
String(s.id) === String(activeSessionId) ? { ...s, workspaceRoot: selected } : s
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const handleStopSession = () => {
|
|
407
|
+
if (handleStop) handleStop(activeSessionId)
|
|
408
|
+
if (window.api && window.api.browserClose) {
|
|
409
|
+
window.api
|
|
410
|
+
.browserClose({
|
|
411
|
+
sessionId: String(activeSessionId) === '1' ? 'main' : `workspace-${activeSessionId}`
|
|
412
|
+
})
|
|
413
|
+
.catch(() => {})
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const handleManualCompaction = async () => {
|
|
418
|
+
if (!currentDisplayMessages || currentDisplayMessages.length <= 1) return
|
|
419
|
+
|
|
420
|
+
const compactBannerId = `compact-banner-${Date.now()}`
|
|
421
|
+
const bannerMsg = {
|
|
422
|
+
id: compactBannerId,
|
|
423
|
+
role: 'system',
|
|
424
|
+
isCompacting: true,
|
|
425
|
+
compactProgress: 'Merangkum konteks percakapan...'
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (String(activeSessionId) === '1') {
|
|
429
|
+
setMainChatData((prev) => [...(prev || []), bannerMsg])
|
|
430
|
+
} else {
|
|
431
|
+
setActiveSessionData((prev) => [...(prev || []), bannerMsg])
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const configList = await db.config.toArray()
|
|
436
|
+
const activeConfig = configList?.[0]?.data || configList?.[0] || {}
|
|
437
|
+
|
|
438
|
+
const { executeSessionCompaction } = await import('../api/ai/contextManager')
|
|
439
|
+
const res = await executeSessionCompaction({
|
|
440
|
+
sessionId: String(activeSessionId),
|
|
441
|
+
messages: currentDisplayMessages.filter((m) => !m.isCompacting),
|
|
442
|
+
activeConfig,
|
|
443
|
+
force: true,
|
|
444
|
+
onProgress: (prog) => {
|
|
445
|
+
const updateFn = (prev) =>
|
|
446
|
+
(prev || []).map((m) =>
|
|
447
|
+
m.id === compactBannerId
|
|
448
|
+
? { ...m, compactProgress: prog?.text || m.compactProgress }
|
|
449
|
+
: m
|
|
450
|
+
)
|
|
451
|
+
if (String(activeSessionId) === '1') {
|
|
452
|
+
setMainChatData(updateFn)
|
|
453
|
+
} else {
|
|
454
|
+
setActiveSessionData(updateFn)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
if (res?.isCompacted) {
|
|
460
|
+
if (res.compactedMessages) {
|
|
461
|
+
const cleanCompacted = res.compactedMessages.filter((m) => m.id !== compactBannerId)
|
|
462
|
+
if (String(activeSessionId) === '1') {
|
|
463
|
+
setMainChatData(cleanCompacted)
|
|
464
|
+
} else {
|
|
465
|
+
setActiveSessionData(cleanCompacted)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (res.lastCompactedMessageId) {
|
|
469
|
+
setLastCompactedMessageId(res.lastCompactedMessageId)
|
|
470
|
+
setActiveSessionCompact({
|
|
471
|
+
summaryBlock: res.newSummaryBlock,
|
|
472
|
+
lastCompactedMessageId: res.lastCompactedMessageId
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
window.dispatchEvent(
|
|
476
|
+
new CustomEvent('session-compact-updated', {
|
|
477
|
+
detail: {
|
|
478
|
+
sessionId: String(activeSessionId),
|
|
479
|
+
lastCompactedMessageId: res.lastCompactedMessageId,
|
|
480
|
+
summaryBlock: res.newSummaryBlock
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
)
|
|
484
|
+
const currentTok = res.currentTokens ?? res.currentChars ?? 0
|
|
485
|
+
const maxTok = res.maxTokens || 256000
|
|
486
|
+
window.dispatchEvent(
|
|
487
|
+
new CustomEvent('context-tracker-updated', {
|
|
488
|
+
detail: {
|
|
489
|
+
sessionId: String(activeSessionId),
|
|
490
|
+
currentTokens: currentTok,
|
|
491
|
+
maxTokens: maxTok,
|
|
492
|
+
currentChars: currentTok,
|
|
493
|
+
maxChars: maxTok,
|
|
494
|
+
percentage: Math.min(100, (currentTok / maxTok) * 100),
|
|
495
|
+
lastCompactedAt: Date.now()
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
} catch (e) {
|
|
501
|
+
console.error('[ChatStudio] Manual compaction error:', e)
|
|
502
|
+
} finally {
|
|
503
|
+
if (String(activeSessionId) === '1') {
|
|
504
|
+
setMainChatData((prev) => (prev || []).filter((m) => m.id !== compactBannerId))
|
|
505
|
+
} else {
|
|
506
|
+
setActiveSessionData((prev) => (prev || []).filter((m) => m.id !== compactBannerId))
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
const handleReqCompact = () => handleManualCompaction()
|
|
513
|
+
window.addEventListener('request-manual-compaction', handleReqCompact)
|
|
514
|
+
return () => window.removeEventListener('request-manual-compaction', handleReqCompact)
|
|
515
|
+
}, [activeSessionId, currentDisplayMessages])
|
|
516
|
+
|
|
517
|
+
const handleClose = () => {
|
|
518
|
+
if (typeof onClose === 'function') {
|
|
519
|
+
onClose()
|
|
520
|
+
} else {
|
|
521
|
+
navigate('/')
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Jika dipanggil via route (tanpa prop isOpen), default ke true
|
|
526
|
+
if (isOpen === false) return null
|
|
527
|
+
|
|
528
|
+
const filteredSessions = sessions.filter((s) =>
|
|
529
|
+
(s.title || '').toLowerCase().includes(searchQuery.toLowerCase())
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
const activeSessionObj = sessions.find((s) => String(s.id) === String(activeSessionId)) || {
|
|
533
|
+
id: '1',
|
|
534
|
+
title: 'Main Thread'
|
|
535
|
+
}
|
|
536
|
+
const activeSessionWorkspace = activeSessionObj?.workspaceRoot || null
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<>
|
|
540
|
+
<div className="fixed inset-0 z-[80] w-screen h-screen bg-base-300 flex overflow-hidden animate-[response-fade-in_0.2s_ease-out_forwards]">
|
|
541
|
+
{loading ? (
|
|
542
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 relative">
|
|
543
|
+
<button
|
|
544
|
+
type="button"
|
|
545
|
+
onClick={handleClose}
|
|
546
|
+
className="absolute top-4 left-4 btn btn-ghost btn-sm btn-circle text-white/70 hover:text-white pointer-events-auto"
|
|
547
|
+
title="Tutup Studio (Esc)"
|
|
548
|
+
>
|
|
549
|
+
<ArrowLeft className="w-5 h-5" />
|
|
550
|
+
</button>
|
|
551
|
+
<span className="loading loading-spinner loading-lg text-primary" />
|
|
552
|
+
<p className="font-mono text-xs text-base-content/50 tracking-widest uppercase">
|
|
553
|
+
Memuat Chat Studio...
|
|
554
|
+
</p>
|
|
555
|
+
</div>
|
|
556
|
+
) : (
|
|
557
|
+
<>
|
|
558
|
+
<div className="w-80 border-r border-white/10 bg-base-200/50 flex flex-col h-full shrink-0">
|
|
559
|
+
{/* Sidebar Header */}
|
|
560
|
+
<div className="p-4 border-b border-white/10 space-y-3 z-30 relative select-none">
|
|
561
|
+
<div className="flex items-center justify-between pointer-events-auto">
|
|
562
|
+
<div className="flex items-center gap-2 font-bold text-sm tracking-wide text-white">
|
|
563
|
+
<button
|
|
564
|
+
type="button"
|
|
565
|
+
onClick={(e) => {
|
|
566
|
+
e.stopPropagation()
|
|
567
|
+
handleClose()
|
|
568
|
+
}}
|
|
569
|
+
className="btn btn-ghost btn-sm btn-circle text-white/70 hover:text-white mr-1 cursor-pointer shrink-0 pointer-events-auto"
|
|
570
|
+
title="Tutup Studio (Esc)"
|
|
571
|
+
>
|
|
572
|
+
<ArrowLeft className="w-5 h-5" />
|
|
573
|
+
</button>
|
|
574
|
+
<span className="truncate">Chat Studio</span>
|
|
575
|
+
</div>
|
|
576
|
+
<div className="flex items-center gap-1 pointer-events-auto">
|
|
577
|
+
<button
|
|
578
|
+
type="button"
|
|
579
|
+
onClick={handleCreateNewChat}
|
|
580
|
+
className="btn btn-xs btn-primary rounded-lg gap-1 font-medium shadow-md shadow-primary/20 cursor-pointer pointer-events-auto"
|
|
581
|
+
>
|
|
582
|
+
<Plus className="w-3.5 h-3.5" />
|
|
583
|
+
Sesi Baru
|
|
584
|
+
</button>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
{/* Search Input */}
|
|
589
|
+
<div className="relative pointer-events-auto">
|
|
590
|
+
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-white/40" />
|
|
591
|
+
<input
|
|
592
|
+
type="text"
|
|
593
|
+
placeholder="Cari obrolan..."
|
|
594
|
+
value={searchQuery}
|
|
595
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
596
|
+
className="input input-sm bg-base-300 border-white/10 pl-9 w-full rounded-xl text-xs text-white placeholder:text-white/30 focus:border-primary/50"
|
|
597
|
+
/>
|
|
598
|
+
</div>
|
|
599
|
+
</div>
|
|
600
|
+
|
|
601
|
+
{/* Sessions Scroll List */}
|
|
602
|
+
<div className="flex-1 overflow-y-auto p-2.5 space-y-1 custom-scrollbar">
|
|
603
|
+
{/* PINNED MAIN THREAD */}
|
|
604
|
+
<div className="px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-white/40 flex items-center gap-1">
|
|
605
|
+
<span>Sesi Utama</span>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div
|
|
609
|
+
role="button"
|
|
610
|
+
tabIndex={0}
|
|
611
|
+
onClick={() => setActiveSessionId('1')}
|
|
612
|
+
onKeyDown={(e) => {
|
|
613
|
+
if (e.key === 'Enter' || e.key === ' ') setActiveSessionId('1')
|
|
614
|
+
}}
|
|
615
|
+
className={`w-full p-2.5 rounded-xl text-left transition-all flex items-center justify-between group/item cursor-pointer ${
|
|
616
|
+
String(activeSessionId) === '1'
|
|
617
|
+
? 'bg-primary/20 border border-primary/40 text-white shadow-sm'
|
|
618
|
+
: 'hover:bg-white/5 text-white/70 hover:text-white border border-transparent'
|
|
619
|
+
}`}
|
|
620
|
+
>
|
|
621
|
+
<div className="flex items-center gap-2.5 min-w-0">
|
|
622
|
+
<div
|
|
623
|
+
className={`w-2 h-2 rounded-full shrink-0 ${
|
|
624
|
+
runningSessionIds.map(String).includes('1') ||
|
|
625
|
+
(!runningSessionIds.length && (isMainLoading || isAgentBusy))
|
|
626
|
+
? 'bg-warning animate-ping'
|
|
627
|
+
: 'bg-primary shadow-[0_0_8px_var(--color-primary)]'
|
|
628
|
+
}`}
|
|
629
|
+
/>
|
|
630
|
+
<div className="min-w-0">
|
|
631
|
+
<h4 className="text-xs font-semibold truncate">Main Thread</h4>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
|
|
636
|
+
<div className="my-2 border-t border-white/5" />
|
|
637
|
+
|
|
638
|
+
{/* WORKSPACE SESSIONS */}
|
|
639
|
+
<div className="px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-white/40">
|
|
640
|
+
Semua Sesi
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
{filteredSessions
|
|
644
|
+
.filter(
|
|
645
|
+
(s) =>
|
|
646
|
+
String(s.id) !== '1' && String(s.id) !== '1.0' && s.title !== 'Main Thread'
|
|
647
|
+
)
|
|
648
|
+
.map((s) => {
|
|
649
|
+
const isActive = String(activeSessionId) === String(s.id)
|
|
650
|
+
const isEditing = String(editingSessionId) === String(s.id)
|
|
651
|
+
const isThisSessionRunning = runningSessionIds
|
|
652
|
+
.map(String)
|
|
653
|
+
.includes(String(s.id))
|
|
654
|
+
|
|
655
|
+
return (
|
|
656
|
+
<div
|
|
657
|
+
key={s.id}
|
|
658
|
+
onClick={() => setActiveSessionId(s.id)}
|
|
659
|
+
className={`w-full p-2.5 rounded-xl text-left transition-all flex items-center justify-between group/session cursor-pointer ${
|
|
660
|
+
isActive
|
|
661
|
+
? 'bg-white/10 border border-white/20 text-white shadow-sm'
|
|
662
|
+
: 'hover:bg-white/5 text-white/70 hover:text-white border border-transparent'
|
|
663
|
+
}`}
|
|
664
|
+
>
|
|
665
|
+
<div className="flex items-center gap-2 min-w-0 flex-1 pr-2">
|
|
666
|
+
<div
|
|
667
|
+
className={`w-2 h-2 rounded-full shrink-0 ${
|
|
668
|
+
isThisSessionRunning
|
|
669
|
+
? 'bg-warning animate-ping'
|
|
670
|
+
: isActive
|
|
671
|
+
? 'bg-primary shadow-[0_0_6px_var(--color-primary)]'
|
|
672
|
+
: 'bg-white/20'
|
|
673
|
+
}`}
|
|
674
|
+
/>
|
|
675
|
+
<MessageSquare className="w-3.5 h-3.5 opacity-50 shrink-0" />
|
|
676
|
+
{isEditing ? (
|
|
677
|
+
<input
|
|
678
|
+
type="text"
|
|
679
|
+
value={editingTitle}
|
|
680
|
+
onChange={(e) => setEditingTitle(e.target.value)}
|
|
681
|
+
onKeyDown={(e) => {
|
|
682
|
+
if (e.key === 'Enter') handleSaveRename(s.id)
|
|
683
|
+
if (e.key === 'Escape') setEditingSessionId(null)
|
|
684
|
+
}}
|
|
685
|
+
autoFocus
|
|
686
|
+
className="input input-xs bg-base-300 border-primary/50 text-xs text-white p-1 h-6 w-full rounded"
|
|
687
|
+
onClick={(e) => e.stopPropagation()}
|
|
688
|
+
/>
|
|
689
|
+
) : (
|
|
690
|
+
<div className="min-w-0 flex-1">
|
|
691
|
+
<h4 className="text-xs font-medium truncate">
|
|
692
|
+
{s.title || 'Percakapan'}
|
|
693
|
+
</h4>
|
|
694
|
+
<p className="text-[10px] opacity-40">
|
|
695
|
+
{s.timestamp
|
|
696
|
+
? new Date(s.timestamp).toLocaleDateString('id-ID', {
|
|
697
|
+
month: 'short',
|
|
698
|
+
day: 'numeric'
|
|
699
|
+
})
|
|
700
|
+
: ''}
|
|
701
|
+
</p>
|
|
702
|
+
</div>
|
|
703
|
+
)}
|
|
704
|
+
</div>
|
|
705
|
+
|
|
706
|
+
{/* Action buttons on hover */}
|
|
707
|
+
<div className="flex items-center gap-1 opacity-0 group-hover/session:opacity-100 transition-opacity">
|
|
708
|
+
{isEditing ? (
|
|
709
|
+
<button
|
|
710
|
+
onClick={(e) => {
|
|
711
|
+
e.stopPropagation()
|
|
712
|
+
handleSaveRename(s.id)
|
|
713
|
+
}}
|
|
714
|
+
className="btn btn-ghost btn-xs p-1 text-success hover:bg-success/20"
|
|
715
|
+
>
|
|
716
|
+
<Check className="w-3 h-3" />
|
|
717
|
+
</button>
|
|
718
|
+
) : (
|
|
719
|
+
<>
|
|
720
|
+
<button
|
|
721
|
+
onClick={(e) => handleStartRename(e, s)}
|
|
722
|
+
className="btn btn-ghost btn-xs p-1 text-white/40 hover:text-white"
|
|
723
|
+
title="Ubah judul sesi"
|
|
724
|
+
>
|
|
725
|
+
<Edit2 className="w-3 h-3" />
|
|
726
|
+
</button>
|
|
727
|
+
<button
|
|
728
|
+
onClick={(e) => handleDeleteSessionClick(e, s.id)}
|
|
729
|
+
className="btn btn-ghost btn-xs p-1 text-white/40 hover:text-error"
|
|
730
|
+
title="Hapus sesi"
|
|
731
|
+
>
|
|
732
|
+
<Trash2 className="w-3 h-3" />
|
|
733
|
+
</button>
|
|
734
|
+
</>
|
|
735
|
+
)}
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
)
|
|
739
|
+
})}
|
|
740
|
+
|
|
741
|
+
{filteredSessions.filter((s) => s.id !== 1).length === 0 && (
|
|
742
|
+
<div className="text-center py-6 text-xs text-white/30">
|
|
743
|
+
Belum ada sesi workspace lain.
|
|
744
|
+
</div>
|
|
745
|
+
)}
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
{/* === RIGHT MAIN: BUBBLE CHAT AREA === */}
|
|
750
|
+
<div className="flex-1 flex flex-col h-full bg-base-300/60 relative min-w-0 overflow-hidden">
|
|
751
|
+
{/* Header */}
|
|
752
|
+
<div className="h-14 px-6 border-b border-white/10 flex items-center justify-between bg-base-300/80 backdrop-blur-md shrink-0 z-30 relative select-none">
|
|
753
|
+
<div className="flex flex-col min-w-0 pr-4">
|
|
754
|
+
<div className="flex items-center gap-2">
|
|
755
|
+
<h3 className="text-sm font-bold text-white truncate max-w-md">
|
|
756
|
+
{activeSessionObj.title || 'Percakapan'}
|
|
757
|
+
</h3>
|
|
758
|
+
{activeSessionWorkspace && (
|
|
759
|
+
<span
|
|
760
|
+
className="badge badge-xs bg-primary/10 text-primary border-primary/30 font-mono text-[9px] px-2 py-0.5 max-w-65 truncate inline-flex items-center gap-1"
|
|
761
|
+
title={`Workspace: ${activeSessionWorkspace}`}
|
|
762
|
+
>
|
|
763
|
+
<Folder className="w-2.5 h-2.5 shrink-0" />
|
|
764
|
+
<span className="truncate">
|
|
765
|
+
{activeSessionWorkspace.split(/[\\/]/).pop()}
|
|
766
|
+
</span>
|
|
767
|
+
</span>
|
|
768
|
+
)}
|
|
769
|
+
</div>
|
|
770
|
+
<span className="text-[10px] text-white/40">
|
|
771
|
+
{currentDisplayMessages.length} pesan terdaftar
|
|
772
|
+
</span>
|
|
773
|
+
</div>
|
|
774
|
+
|
|
775
|
+
<div className="flex items-center gap-2 pointer-events-auto mr-28"></div>
|
|
776
|
+
</div>
|
|
777
|
+
|
|
778
|
+
{/* Messages Stream Container */}
|
|
779
|
+
<div
|
|
780
|
+
ref={messagesContainerRef}
|
|
781
|
+
onScroll={handleScroll}
|
|
782
|
+
className="flex-1 overflow-y-auto overflow-x-hidden px-4 md:px-8 py-6 custom-scrollbar min-h-0"
|
|
783
|
+
>
|
|
784
|
+
<div className="max-w-6xl mx-auto w-full min-h-full flex flex-col space-y-8">
|
|
785
|
+
{currentDisplayMessages.length > visibleMessageCount && (
|
|
786
|
+
<div className="flex justify-center py-2">
|
|
787
|
+
<button
|
|
788
|
+
type="button"
|
|
789
|
+
onClick={() => setVisibleMessageCount((prev) => prev + 30)}
|
|
790
|
+
className="btn btn-xs btn-ghost text-[11px] text-white/50 hover:text-white border border-white/10 rounded-full px-4 normal-case cursor-pointer"
|
|
791
|
+
>
|
|
792
|
+
Muat pesan sebelumnya ({currentDisplayMessages.length - visibleMessageCount}{' '}
|
|
793
|
+
pesan lagi)
|
|
794
|
+
</button>
|
|
795
|
+
</div>
|
|
796
|
+
)}
|
|
797
|
+
|
|
798
|
+
{currentDisplayMessages.length === 0 ? (
|
|
799
|
+
<div className="h-full flex flex-col items-center justify-center text-center p-8 text-white/40 space-y-4 my-auto">
|
|
800
|
+
<img src="/icon-256.png" alt="icon" className="size-30" />
|
|
801
|
+
<div className="max-w-lg space-y-1">
|
|
802
|
+
<h4 className="text-xl font-bold text-white">
|
|
803
|
+
Selamat Datang di Mark Agent
|
|
804
|
+
</h4>
|
|
805
|
+
<p className="text-lg text-white/50">
|
|
806
|
+
Tanyakan apapun, analisis kode, atau diskusikan ide riset bersama Mark.
|
|
807
|
+
</p>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
) : (
|
|
811
|
+
(() => {
|
|
812
|
+
const hasActivePlan = (currentDisplayMessages || []).some(
|
|
813
|
+
(m) => m.isPlanSteps && m.taskStatus === 'running'
|
|
814
|
+
)
|
|
815
|
+
const activeThinkingMsg =
|
|
816
|
+
[...(currentDisplayMessages || [])].reverse().find((m) => m.isThinking) ||
|
|
817
|
+
null
|
|
818
|
+
|
|
819
|
+
return currentDisplayMessages.slice(-visibleMessageCount).map((msg, idx) => {
|
|
820
|
+
if (hasActivePlan && msg.isThinking && !msg.isPlanSteps) {
|
|
821
|
+
return null
|
|
822
|
+
}
|
|
823
|
+
const { cleanContent } = filterMessagePrefixes(msg.content)
|
|
824
|
+
return (
|
|
825
|
+
<ChatList
|
|
826
|
+
key={
|
|
827
|
+
msg.id
|
|
828
|
+
? `${msg.id}-${idx}`
|
|
829
|
+
: `${msg.created_at || msg.timestamp || 'msg'}-${idx}`
|
|
830
|
+
}
|
|
831
|
+
id={msg.id || msg.timestamp || msg.created_at}
|
|
832
|
+
lastCompactedMessageId={lastCompactedMessageId}
|
|
833
|
+
isCompacting={msg.isCompacting}
|
|
834
|
+
compactProgress={msg.compactProgress}
|
|
835
|
+
role={msg.role}
|
|
836
|
+
content={cleanContent}
|
|
837
|
+
reasoning={msg.reasoning}
|
|
838
|
+
isThinking={msg.isThinking}
|
|
839
|
+
isSearching={msg.isSearching}
|
|
840
|
+
isSummarizing={msg.isSummarizing}
|
|
841
|
+
isSearchingMusic={msg.isSearchingMusic}
|
|
842
|
+
sources={msg.sources}
|
|
843
|
+
executedTools={msg.executedTools}
|
|
844
|
+
isMemorySaved={msg.isMemorySaved}
|
|
845
|
+
isMemoryUpdated={msg.isMemoryUpdated}
|
|
846
|
+
isMemoryDeleted={msg.isMemoryDeleted}
|
|
847
|
+
timestamp={msg.timestamp}
|
|
848
|
+
mood={msg.mood}
|
|
849
|
+
source={msg.source}
|
|
850
|
+
sender={msg.sender}
|
|
851
|
+
isPlanSteps={msg.isPlanSteps}
|
|
852
|
+
plan={msg.plan}
|
|
853
|
+
currentStep={msg.currentStep}
|
|
854
|
+
taskId={msg.taskId}
|
|
855
|
+
taskTitle={msg.taskTitle}
|
|
856
|
+
taskObjective={msg.taskObjective}
|
|
857
|
+
taskStatus={msg.taskStatus}
|
|
858
|
+
artifactRoot={msg.artifactRoot}
|
|
859
|
+
activeLiveTools={
|
|
860
|
+
hasActivePlan && msg.isPlanSteps
|
|
861
|
+
? activeThinkingMsg?.executedTools || null
|
|
862
|
+
: null
|
|
863
|
+
}
|
|
864
|
+
activeThinkingContent={
|
|
865
|
+
hasActivePlan && msg.isPlanSteps
|
|
866
|
+
? (activeThinkingMsg?.reasoning
|
|
867
|
+
? activeThinkingMsg.reasoning
|
|
868
|
+
.split('\n')
|
|
869
|
+
.map((s) => s.trim())
|
|
870
|
+
.filter(Boolean)
|
|
871
|
+
.pop()
|
|
872
|
+
: null) ||
|
|
873
|
+
activeThinkingMsg?.content ||
|
|
874
|
+
null
|
|
875
|
+
: null
|
|
876
|
+
}
|
|
877
|
+
onStop={handleStopSession}
|
|
878
|
+
isApproval={msg.isApproval}
|
|
879
|
+
approvalId={msg.approvalId}
|
|
880
|
+
approvalTool={msg.tool}
|
|
881
|
+
approvalQuery={msg.query}
|
|
882
|
+
approvalStatus={msg.status}
|
|
883
|
+
/>
|
|
884
|
+
)
|
|
885
|
+
})
|
|
886
|
+
})()
|
|
887
|
+
)}
|
|
888
|
+
<div ref={messagesEndRef} className="h-2" />
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
|
|
892
|
+
{/* Bottom Input Area */}
|
|
893
|
+
<div className="p-3 border-t border-white/10 bg-base-200/40 shrink-0">
|
|
894
|
+
<div className="max-w-6xl mx-auto w-full h-full">
|
|
895
|
+
<InputBar
|
|
896
|
+
sessionId={String(activeSessionId)}
|
|
897
|
+
inline={true}
|
|
898
|
+
onSubmit={handleSendMessage}
|
|
899
|
+
isLoading={isCurrentLoading}
|
|
900
|
+
isRecording={isRecording}
|
|
901
|
+
isProcessing={isProcessing}
|
|
902
|
+
audioIntensity={audioIntensity}
|
|
903
|
+
onStartRecord={startRecording}
|
|
904
|
+
onStopRecord={stopRecording}
|
|
905
|
+
onStop={handleStopSession}
|
|
906
|
+
source={inputSource || 'pc'}
|
|
907
|
+
workspaceRoot={activeSessionWorkspace}
|
|
908
|
+
onSelectWorkspace={handleSelectSessionWorkspace}
|
|
909
|
+
onManualCompact={handleManualCompaction}
|
|
910
|
+
/>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
</>
|
|
915
|
+
)}
|
|
916
|
+
</div>
|
|
917
|
+
|
|
918
|
+
<ModalComponent />
|
|
919
|
+
</>
|
|
920
|
+
)
|
|
921
|
+
}
|
|
922
|
+
export default ChatStudio
|