@adiholick/nvstudio-mcp 1.0.3 → 1.1.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/dist/index.js +47 -11
- package/dist/roblox-bridge.js +64 -5
- package/package.json +3 -2
- package/studio-plugin/Main.server.lua +291 -0
- package/studio-plugin/create_instance.lua +24 -0
- package/studio-plugin/delete_instance.lua +31 -0
- package/studio-plugin/execute_luau.lua +71 -0
- package/studio-plugin/generate_terrain.lua +23 -0
- package/studio-plugin/get_children.lua +7 -0
- package/studio-plugin/get_logs.lua +7 -0
- package/studio-plugin/get_properties.lua +52 -0
- package/studio-plugin/get_script_source.lua +7 -0
- package/studio-plugin/insert_asset.lua +18 -0
- package/studio-plugin/nvstudio_mcp.rbxmx +1337 -0
- package/studio-plugin/rollback_script.lua +13 -0
- package/studio-plugin/script_grep.lua +63 -0
- package/studio-plugin/search_instance.lua +20 -0
- package/studio-plugin/ui_manager.lua +443 -0
- package/studio-plugin/update_script_source.lua +16 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
return function(targetInstance, data, ctx, targetPath)
|
|
2
|
+
if targetInstance:IsA("LuaSourceContainer") then
|
|
3
|
+
local prevSource = ctx.scriptHistory[targetPath]
|
|
4
|
+
if prevSource then
|
|
5
|
+
targetInstance.Source = prevSource
|
|
6
|
+
return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil di-rollback ke versi sebelumnya." }
|
|
7
|
+
else
|
|
8
|
+
return { status = "error", error = "Tidak ada riwayat backup sebelumnya di sesi Studio ini untuk: " .. targetPath }
|
|
9
|
+
end
|
|
10
|
+
else
|
|
11
|
+
return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
return function(targetInstance, data, ctx, targetPath)
|
|
2
|
+
local query = tostring(data)
|
|
3
|
+
if not query or query == "" then
|
|
4
|
+
return { status = "error", error = "Query pencarian tidak boleh kosong." }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
local matches = {}
|
|
8
|
+
local matchCount = 0
|
|
9
|
+
local MAX_MATCHES = 50
|
|
10
|
+
|
|
11
|
+
local searchableServices = {
|
|
12
|
+
game:GetService("Workspace"),
|
|
13
|
+
game:GetService("ServerScriptService"),
|
|
14
|
+
game:GetService("ReplicatedStorage"),
|
|
15
|
+
game:GetService("StarterPlayer"),
|
|
16
|
+
game:GetService("StarterGui")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
local function searchInScript(scriptObj)
|
|
20
|
+
local success, source = pcall(function() return scriptObj.Source end)
|
|
21
|
+
if not success or not source then return end
|
|
22
|
+
|
|
23
|
+
local lines = string.split(source, "\n")
|
|
24
|
+
for i, line in ipairs(lines) do
|
|
25
|
+
-- Coba cari dengan plain text dulu, jika gagal coba pattern
|
|
26
|
+
local found = string.find(line, query, 1, true)
|
|
27
|
+
if not found then
|
|
28
|
+
pcall(function() found = string.find(line, query) end)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if found then
|
|
32
|
+
table.insert(matches, {
|
|
33
|
+
script = scriptObj:GetFullName(),
|
|
34
|
+
line = i,
|
|
35
|
+
content = string.match(line, "^%s*(.-)%s*$") or line
|
|
36
|
+
})
|
|
37
|
+
matchCount = matchCount + 1
|
|
38
|
+
if matchCount >= MAX_MATCHES then
|
|
39
|
+
return true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
for _, service in ipairs(searchableServices) do
|
|
47
|
+
pcall(function()
|
|
48
|
+
local descendants = service:GetDescendants()
|
|
49
|
+
for _, desc in ipairs(descendants) do
|
|
50
|
+
if desc:IsA("LuaSourceContainer") then
|
|
51
|
+
if searchInScript(desc) then
|
|
52
|
+
break
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end)
|
|
57
|
+
if matchCount >= MAX_MATCHES then
|
|
58
|
+
break
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return { status = "success", matches = matches, count = matchCount, limitReached = (matchCount >= MAX_MATCHES) }
|
|
63
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
return function(targetInstance, data, ctx, targetPath)
|
|
2
|
+
local searchName = tostring(data)
|
|
3
|
+
local results = {}
|
|
4
|
+
|
|
5
|
+
local searchableServices = {game:GetService("Workspace"), game:GetService("ReplicatedStorage"), game:GetService("ServerScriptService"), game:GetService("StarterGui")}
|
|
6
|
+
|
|
7
|
+
for _, service in ipairs(searchableServices) do
|
|
8
|
+
for _, desc in ipairs(service:GetDescendants()) do
|
|
9
|
+
if desc.Name == searchName then
|
|
10
|
+
table.insert(results, desc:GetFullName())
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if #results > 0 then
|
|
16
|
+
return { status = "success", result = results }
|
|
17
|
+
else
|
|
18
|
+
return { status = "success", result = "Tidak ditemukan instance dengan nama: " .. searchName }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
local UIManager = {}
|
|
2
|
+
local widget = nil
|
|
3
|
+
|
|
4
|
+
function UIManager.init(pluginInstance, studioId)
|
|
5
|
+
-- Membuat Widget Panel yang bisa di-dock (menempel)
|
|
6
|
+
local widgetInfo = DockWidgetPluginGuiInfo.new(
|
|
7
|
+
Enum.InitialDockState.Right,
|
|
8
|
+
false, -- Initially hidden until toolbar button is clicked
|
|
9
|
+
false, -- Override enabled
|
|
10
|
+
380, -- Default width
|
|
11
|
+
520, -- Default height
|
|
12
|
+
280, -- Min width
|
|
13
|
+
320 -- Min height
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
widget = pluginInstance:CreateDockWidgetPluginGui("NVStudioMCP_GUI_V3", widgetInfo)
|
|
17
|
+
widget.Title = "NVStudio MCP Dashboard"
|
|
18
|
+
|
|
19
|
+
-- ═══════════════════════════════════════════
|
|
20
|
+
-- ROOT FRAME (Non-scrolling, fills entire widget)
|
|
21
|
+
-- ═══════════════════════════════════════════
|
|
22
|
+
local rootFrame = Instance.new("Frame")
|
|
23
|
+
rootFrame.Size = UDim2.new(1, 0, 1, 0)
|
|
24
|
+
rootFrame.BackgroundColor3 = Color3.fromRGB(22, 22, 26)
|
|
25
|
+
rootFrame.BorderSizePixel = 0
|
|
26
|
+
rootFrame.Parent = widget
|
|
27
|
+
|
|
28
|
+
local rootPadding = Instance.new("UIPadding")
|
|
29
|
+
rootPadding.PaddingTop = UDim.new(0, 8)
|
|
30
|
+
rootPadding.PaddingLeft = UDim.new(0, 10)
|
|
31
|
+
rootPadding.PaddingRight = UDim.new(0, 10)
|
|
32
|
+
rootPadding.PaddingBottom = UDim.new(0, 8)
|
|
33
|
+
rootPadding.Parent = rootFrame
|
|
34
|
+
|
|
35
|
+
local rootLayout = Instance.new("UIListLayout")
|
|
36
|
+
rootLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
37
|
+
rootLayout.Padding = UDim.new(0, 6)
|
|
38
|
+
rootLayout.Parent = rootFrame
|
|
39
|
+
|
|
40
|
+
-- ═══════════════════════════════════════════
|
|
41
|
+
-- SECTION 1: HEADER CARD
|
|
42
|
+
-- ═══════════════════════════════════════════
|
|
43
|
+
local headerFrame = Instance.new("Frame")
|
|
44
|
+
headerFrame.AutomaticSize = Enum.AutomaticSize.Y
|
|
45
|
+
headerFrame.Size = UDim2.new(1, 0, 0, 0)
|
|
46
|
+
headerFrame.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
47
|
+
headerFrame.BorderSizePixel = 0
|
|
48
|
+
headerFrame.LayoutOrder = 1
|
|
49
|
+
headerFrame.Parent = rootFrame
|
|
50
|
+
|
|
51
|
+
local hCorner = Instance.new("UICorner")
|
|
52
|
+
hCorner.CornerRadius = UDim.new(0, 6)
|
|
53
|
+
hCorner.Parent = headerFrame
|
|
54
|
+
|
|
55
|
+
local hPadding = Instance.new("UIPadding")
|
|
56
|
+
hPadding.PaddingTop = UDim.new(0, 10)
|
|
57
|
+
hPadding.PaddingLeft = UDim.new(0, 12)
|
|
58
|
+
hPadding.PaddingRight = UDim.new(0, 12)
|
|
59
|
+
hPadding.PaddingBottom = UDim.new(0, 10)
|
|
60
|
+
hPadding.Parent = headerFrame
|
|
61
|
+
|
|
62
|
+
local hLayout = Instance.new("UIListLayout")
|
|
63
|
+
hLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
64
|
+
hLayout.Padding = UDim.new(0, 2)
|
|
65
|
+
hLayout.Parent = headerFrame
|
|
66
|
+
|
|
67
|
+
-- Title Row (Name + Status side by side)
|
|
68
|
+
local titleRow = Instance.new("Frame")
|
|
69
|
+
titleRow.AutomaticSize = Enum.AutomaticSize.Y
|
|
70
|
+
titleRow.Size = UDim2.new(1, 0, 0, 0)
|
|
71
|
+
titleRow.BackgroundTransparency = 1
|
|
72
|
+
titleRow.LayoutOrder = 1
|
|
73
|
+
titleRow.Parent = headerFrame
|
|
74
|
+
|
|
75
|
+
local titleLabel = Instance.new("TextLabel")
|
|
76
|
+
titleLabel.Text = "NVStudio MCP"
|
|
77
|
+
titleLabel.Font = Enum.Font.GothamBold
|
|
78
|
+
titleLabel.TextSize = 18
|
|
79
|
+
titleLabel.TextColor3 = Color3.fromRGB(240, 240, 245)
|
|
80
|
+
titleLabel.BackgroundTransparency = 1
|
|
81
|
+
titleLabel.Size = UDim2.new(0.6, 0, 0, 22)
|
|
82
|
+
titleLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
83
|
+
titleLabel.Parent = titleRow
|
|
84
|
+
|
|
85
|
+
local statusLabel = Instance.new("TextLabel")
|
|
86
|
+
statusLabel.Text = "● Disconnected"
|
|
87
|
+
statusLabel.Font = Enum.Font.GothamBold
|
|
88
|
+
statusLabel.TextSize = 12
|
|
89
|
+
statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
|
|
90
|
+
statusLabel.BackgroundTransparency = 1
|
|
91
|
+
statusLabel.Size = UDim2.new(0.4, 0, 0, 22)
|
|
92
|
+
statusLabel.Position = UDim2.new(0.6, 0, 0, 0)
|
|
93
|
+
statusLabel.TextXAlignment = Enum.TextXAlignment.Right
|
|
94
|
+
statusLabel.Parent = titleRow
|
|
95
|
+
|
|
96
|
+
-- Version
|
|
97
|
+
local versionLabel = Instance.new("TextLabel")
|
|
98
|
+
versionLabel.Text = "v1.2.0 · Hybrid Agent"
|
|
99
|
+
versionLabel.Font = Enum.Font.Gotham
|
|
100
|
+
versionLabel.TextSize = 11
|
|
101
|
+
versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)
|
|
102
|
+
versionLabel.BackgroundTransparency = 1
|
|
103
|
+
versionLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
104
|
+
versionLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
105
|
+
versionLabel.LayoutOrder = 2
|
|
106
|
+
versionLabel.Parent = headerFrame
|
|
107
|
+
|
|
108
|
+
-- Thin separator
|
|
109
|
+
local sep1 = Instance.new("Frame")
|
|
110
|
+
sep1.Size = UDim2.new(1, 0, 0, 1)
|
|
111
|
+
sep1.BackgroundColor3 = Color3.fromRGB(50, 50, 58)
|
|
112
|
+
sep1.BorderSizePixel = 0
|
|
113
|
+
sep1.LayoutOrder = 3
|
|
114
|
+
sep1.Parent = headerFrame
|
|
115
|
+
|
|
116
|
+
-- Place info
|
|
117
|
+
local placeLabel = Instance.new("TextLabel")
|
|
118
|
+
placeLabel.Text = "📁 " .. (game.Name ~= "" and game.Name or "Untitled Baseplate")
|
|
119
|
+
placeLabel.Font = Enum.Font.Gotham
|
|
120
|
+
placeLabel.TextSize = 11
|
|
121
|
+
placeLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
|
|
122
|
+
placeLabel.BackgroundTransparency = 1
|
|
123
|
+
placeLabel.Size = UDim2.new(1, 0, 0, 16)
|
|
124
|
+
placeLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
125
|
+
placeLabel.LayoutOrder = 4
|
|
126
|
+
placeLabel.Parent = headerFrame
|
|
127
|
+
|
|
128
|
+
-- Studio ID
|
|
129
|
+
local idLabel = Instance.new("TextLabel")
|
|
130
|
+
idLabel.Text = "🔗 " .. string.sub(tostring(studioId), 1, 18) .. "..."
|
|
131
|
+
idLabel.Font = Enum.Font.Code
|
|
132
|
+
idLabel.TextSize = 10
|
|
133
|
+
idLabel.TextColor3 = Color3.fromRGB(90, 180, 230)
|
|
134
|
+
idLabel.BackgroundTransparency = 1
|
|
135
|
+
idLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
136
|
+
idLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
137
|
+
idLabel.LayoutOrder = 5
|
|
138
|
+
idLabel.Parent = headerFrame
|
|
139
|
+
|
|
140
|
+
-- Server URL
|
|
141
|
+
local serverLabel = Instance.new("TextLabel")
|
|
142
|
+
serverLabel.Text = "🌐 localhost:3055"
|
|
143
|
+
serverLabel.Font = Enum.Font.Code
|
|
144
|
+
serverLabel.TextSize = 10
|
|
145
|
+
serverLabel.TextColor3 = Color3.fromRGB(140, 140, 150)
|
|
146
|
+
serverLabel.BackgroundTransparency = 1
|
|
147
|
+
serverLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
148
|
+
serverLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
149
|
+
serverLabel.LayoutOrder = 6
|
|
150
|
+
serverLabel.Parent = headerFrame
|
|
151
|
+
|
|
152
|
+
-- ═══════════════════════════════════════════
|
|
153
|
+
-- SECTION 2: CONTROLS ROW
|
|
154
|
+
-- ═══════════════════════════════════════════
|
|
155
|
+
local controlsFrame = Instance.new("Frame")
|
|
156
|
+
controlsFrame.Size = UDim2.new(1, 0, 0, 32)
|
|
157
|
+
controlsFrame.BackgroundTransparency = 1
|
|
158
|
+
controlsFrame.LayoutOrder = 2
|
|
159
|
+
controlsFrame.Parent = rootFrame
|
|
160
|
+
|
|
161
|
+
-- Connect / Disconnect Button
|
|
162
|
+
local connectBtn = Instance.new("TextButton")
|
|
163
|
+
connectBtn.Size = UDim2.new(0.52, -4, 1, 0)
|
|
164
|
+
connectBtn.Position = UDim2.new(0, 0, 0, 0)
|
|
165
|
+
connectBtn.Text = " Connect"
|
|
166
|
+
connectBtn.Font = Enum.Font.GothamBold
|
|
167
|
+
connectBtn.TextSize = 13
|
|
168
|
+
connectBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
|
|
169
|
+
connectBtn.BackgroundColor3 = Color3.fromRGB(40, 160, 80)
|
|
170
|
+
connectBtn.BorderSizePixel = 0
|
|
171
|
+
connectBtn.AutoButtonColor = false
|
|
172
|
+
connectBtn.ZIndex = 2
|
|
173
|
+
connectBtn.Parent = controlsFrame
|
|
174
|
+
|
|
175
|
+
local cCorner = Instance.new("UICorner")
|
|
176
|
+
cCorner.CornerRadius = UDim.new(0, 5)
|
|
177
|
+
cCorner.Parent = connectBtn
|
|
178
|
+
|
|
179
|
+
-- Hover Effects for Connect Button (menggunakan stored base color, bukan text check)
|
|
180
|
+
local _btnBaseColor = Color3.fromRGB(40, 160, 80)
|
|
181
|
+
local _isHovering = false
|
|
182
|
+
|
|
183
|
+
connectBtn.MouseEnter:Connect(function()
|
|
184
|
+
_isHovering = true
|
|
185
|
+
connectBtn.BackgroundColor3 = Color3.fromRGB(
|
|
186
|
+
math.min(255, math.floor(_btnBaseColor.R * 255 + 25)),
|
|
187
|
+
math.min(255, math.floor(_btnBaseColor.G * 255 + 25)),
|
|
188
|
+
math.min(255, math.floor(_btnBaseColor.B * 255 + 25))
|
|
189
|
+
)
|
|
190
|
+
end)
|
|
191
|
+
connectBtn.MouseLeave:Connect(function()
|
|
192
|
+
_isHovering = false
|
|
193
|
+
connectBtn.BackgroundColor3 = _btnBaseColor
|
|
194
|
+
end)
|
|
195
|
+
|
|
196
|
+
-- Auto-Connect Checkbox Container
|
|
197
|
+
local autoConnectContainer = Instance.new("Frame")
|
|
198
|
+
autoConnectContainer.Size = UDim2.new(0.48, -4, 1, 0)
|
|
199
|
+
autoConnectContainer.Position = UDim2.new(0.52, 4, 0, 0)
|
|
200
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
201
|
+
autoConnectContainer.BorderSizePixel = 0
|
|
202
|
+
autoConnectContainer.Parent = controlsFrame
|
|
203
|
+
|
|
204
|
+
local acCorner = Instance.new("UICorner")
|
|
205
|
+
acCorner.CornerRadius = UDim.new(0, 5)
|
|
206
|
+
acCorner.Parent = autoConnectContainer
|
|
207
|
+
|
|
208
|
+
-- Checkbox visual (outer box)
|
|
209
|
+
local checkboxOuter = Instance.new("Frame")
|
|
210
|
+
checkboxOuter.Size = UDim2.new(0, 16, 0, 16)
|
|
211
|
+
checkboxOuter.Position = UDim2.new(0, 10, 0.5, -8)
|
|
212
|
+
checkboxOuter.BackgroundColor3 = Color3.fromRGB(55, 55, 65)
|
|
213
|
+
checkboxOuter.BorderSizePixel = 0
|
|
214
|
+
checkboxOuter.Parent = autoConnectContainer
|
|
215
|
+
|
|
216
|
+
local cbCorner = Instance.new("UICorner")
|
|
217
|
+
cbCorner.CornerRadius = UDim.new(0, 3)
|
|
218
|
+
cbCorner.Parent = checkboxOuter
|
|
219
|
+
|
|
220
|
+
local cbStroke = Instance.new("UIStroke")
|
|
221
|
+
cbStroke.Color = Color3.fromRGB(80, 80, 95)
|
|
222
|
+
cbStroke.Thickness = 1
|
|
223
|
+
cbStroke.Parent = checkboxOuter
|
|
224
|
+
|
|
225
|
+
-- Checkbox inner (check mark)
|
|
226
|
+
local checkboxInner = Instance.new("Frame")
|
|
227
|
+
checkboxInner.Size = UDim2.new(0, 10, 0, 10)
|
|
228
|
+
checkboxInner.Position = UDim2.new(0.5, -5, 0.5, -5)
|
|
229
|
+
checkboxInner.BackgroundColor3 = Color3.fromRGB(80, 170, 255)
|
|
230
|
+
checkboxInner.Visible = false
|
|
231
|
+
checkboxInner.Parent = checkboxOuter
|
|
232
|
+
|
|
233
|
+
local cbiCorner = Instance.new("UICorner")
|
|
234
|
+
cbiCorner.CornerRadius = UDim.new(0, 2)
|
|
235
|
+
cbiCorner.Parent = checkboxInner
|
|
236
|
+
|
|
237
|
+
-- Label
|
|
238
|
+
local autoConnectLabel = Instance.new("TextLabel")
|
|
239
|
+
autoConnectLabel.Text = "Auto-Connect"
|
|
240
|
+
autoConnectLabel.Font = Enum.Font.Gotham
|
|
241
|
+
autoConnectLabel.TextSize = 11
|
|
242
|
+
autoConnectLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
|
|
243
|
+
autoConnectLabel.BackgroundTransparency = 1
|
|
244
|
+
autoConnectLabel.Position = UDim2.new(0, 32, 0, 0)
|
|
245
|
+
autoConnectLabel.Size = UDim2.new(1, -36, 1, 0)
|
|
246
|
+
autoConnectLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
247
|
+
autoConnectLabel.Parent = autoConnectContainer
|
|
248
|
+
|
|
249
|
+
-- Invisible overlay button for checkbox hit area
|
|
250
|
+
local autoConnectBtn = Instance.new("TextButton")
|
|
251
|
+
autoConnectBtn.Size = UDim2.new(1, 0, 1, 0)
|
|
252
|
+
autoConnectBtn.BackgroundTransparency = 1
|
|
253
|
+
autoConnectBtn.Text = ""
|
|
254
|
+
autoConnectBtn.ZIndex = 3 -- Di atas elemen visual agar klik selalu terdeteksi
|
|
255
|
+
autoConnectBtn.Parent = autoConnectContainer
|
|
256
|
+
|
|
257
|
+
-- Hover effect for checkbox container
|
|
258
|
+
autoConnectBtn.MouseEnter:Connect(function()
|
|
259
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(42, 42, 50)
|
|
260
|
+
end)
|
|
261
|
+
autoConnectBtn.MouseLeave:Connect(function()
|
|
262
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
263
|
+
end)
|
|
264
|
+
|
|
265
|
+
-- ═══════════════════════════════════════════
|
|
266
|
+
-- SECTION 3: TERMINAL LOG (fills remaining space)
|
|
267
|
+
-- ═══════════════════════════════════════════
|
|
268
|
+
-- Terminal Label
|
|
269
|
+
local termLabel = Instance.new("TextLabel")
|
|
270
|
+
termLabel.Text = "TERMINAL"
|
|
271
|
+
termLabel.Font = Enum.Font.GothamBold
|
|
272
|
+
termLabel.TextSize = 10
|
|
273
|
+
termLabel.TextColor3 = Color3.fromRGB(90, 90, 100)
|
|
274
|
+
termLabel.BackgroundTransparency = 1
|
|
275
|
+
termLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
276
|
+
termLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
277
|
+
termLabel.LayoutOrder = 3
|
|
278
|
+
termLabel.Parent = rootFrame
|
|
279
|
+
|
|
280
|
+
-- Terminal container: Fills ALL remaining vertical space
|
|
281
|
+
local terminalContainer = Instance.new("Frame")
|
|
282
|
+
terminalContainer.BackgroundColor3 = Color3.fromRGB(14, 14, 18)
|
|
283
|
+
terminalContainer.BorderSizePixel = 0
|
|
284
|
+
terminalContainer.LayoutOrder = 4
|
|
285
|
+
terminalContainer.ClipsDescendants = true
|
|
286
|
+
terminalContainer.Parent = rootFrame
|
|
287
|
+
|
|
288
|
+
local termCorner = Instance.new("UICorner")
|
|
289
|
+
termCorner.CornerRadius = UDim.new(0, 5)
|
|
290
|
+
termCorner.Parent = terminalContainer
|
|
291
|
+
|
|
292
|
+
local termStroke = Instance.new("UIStroke")
|
|
293
|
+
termStroke.Color = Color3.fromRGB(40, 40, 48)
|
|
294
|
+
termStroke.Thickness = 1
|
|
295
|
+
termStroke.Parent = terminalContainer
|
|
296
|
+
|
|
297
|
+
-- Scrolling log area inside terminal
|
|
298
|
+
local scrollingFrame = Instance.new("ScrollingFrame")
|
|
299
|
+
scrollingFrame.Size = UDim2.new(1, -8, 1, -4)
|
|
300
|
+
scrollingFrame.Position = UDim2.new(0, 4, 0, 2)
|
|
301
|
+
scrollingFrame.BackgroundTransparency = 1
|
|
302
|
+
scrollingFrame.BorderSizePixel = 0
|
|
303
|
+
scrollingFrame.ScrollBarThickness = 4
|
|
304
|
+
scrollingFrame.ScrollBarImageColor3 = Color3.fromRGB(70, 70, 85)
|
|
305
|
+
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
|
|
306
|
+
scrollingFrame.Parent = terminalContainer
|
|
307
|
+
|
|
308
|
+
local listLayout = Instance.new("UIListLayout")
|
|
309
|
+
listLayout.Padding = UDim.new(0, 1)
|
|
310
|
+
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
311
|
+
listLayout.Parent = scrollingFrame
|
|
312
|
+
|
|
313
|
+
-- ═══════════════════════════════════════════
|
|
314
|
+
-- RESPONSIVITY: Dynamically size terminal to fill remaining space
|
|
315
|
+
-- ═══════════════════════════════════════════
|
|
316
|
+
local function updateLayout()
|
|
317
|
+
-- Calculate how much space header + controls + termLabel use
|
|
318
|
+
local usedHeight = rootLayout.AbsoluteContentSize.Y - terminalContainer.AbsoluteSize.Y
|
|
319
|
+
local availableHeight = rootFrame.AbsoluteSize.Y - 16 -- rootPadding top+bottom
|
|
320
|
+
local termHeight = math.max(100, availableHeight - usedHeight)
|
|
321
|
+
terminalContainer.Size = UDim2.new(1, 0, 0, termHeight)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
-- Fire on widget resize
|
|
325
|
+
widget:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateLayout)
|
|
326
|
+
-- Also fire once after initial render
|
|
327
|
+
task.defer(updateLayout)
|
|
328
|
+
|
|
329
|
+
-- ═══════════════════════════════════════════
|
|
330
|
+
-- Store references for external access
|
|
331
|
+
-- ═══════════════════════════════════════════
|
|
332
|
+
UIManager.widget = widget
|
|
333
|
+
UIManager.connectBtn = connectBtn
|
|
334
|
+
UIManager.autoConnectBtn = autoConnectBtn
|
|
335
|
+
UIManager.checkboxInner = checkboxInner
|
|
336
|
+
UIManager.checkboxStroke = cbStroke
|
|
337
|
+
UIManager.statusLabel = statusLabel
|
|
338
|
+
UIManager.scrollingFrame = scrollingFrame
|
|
339
|
+
UIManager.listLayout = listLayout
|
|
340
|
+
UIManager.terminalContainer = terminalContainer
|
|
341
|
+
UIManager.termStroke = termStroke
|
|
342
|
+
UIManager.logsCount = 0
|
|
343
|
+
|
|
344
|
+
-- Simpan referensi hover state agar setStatus bisa update base color
|
|
345
|
+
UIManager._btnBaseColor = _btnBaseColor
|
|
346
|
+
UIManager._isHovering = _isHovering
|
|
347
|
+
UIManager._updateBtnBaseColor = function(newColor)
|
|
348
|
+
_btnBaseColor = newColor
|
|
349
|
+
if not _isHovering then
|
|
350
|
+
connectBtn.BackgroundColor3 = newColor
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
return UIManager
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
-- ═══════════════════════════════════════════
|
|
358
|
+
-- Menambahkan baris log baru ke terminal GUI
|
|
359
|
+
-- ═══════════════════════════════════════════
|
|
360
|
+
function UIManager:addLog(text, color)
|
|
361
|
+
local timestamp = os.date("%H:%M:%S")
|
|
362
|
+
local displayText = "[" .. timestamp .. "] " .. text
|
|
363
|
+
|
|
364
|
+
local logLabel = Instance.new("TextLabel")
|
|
365
|
+
logLabel.Text = displayText
|
|
366
|
+
logLabel.Font = Enum.Font.Code
|
|
367
|
+
logLabel.TextSize = 11
|
|
368
|
+
logLabel.TextColor3 = color or Color3.fromRGB(180, 180, 190)
|
|
369
|
+
logLabel.BackgroundTransparency = 1
|
|
370
|
+
logLabel.TextWrapped = true
|
|
371
|
+
logLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
372
|
+
logLabel.RichText = false
|
|
373
|
+
logLabel.AutomaticSize = Enum.AutomaticSize.Y
|
|
374
|
+
logLabel.Size = UDim2.new(1, -6, 0, 0) -- Width fill, height auto
|
|
375
|
+
|
|
376
|
+
local logPadding = Instance.new("UIPadding")
|
|
377
|
+
logPadding.PaddingLeft = UDim.new(0, 4)
|
|
378
|
+
logPadding.PaddingTop = UDim.new(0, 1)
|
|
379
|
+
logPadding.PaddingBottom = UDim.new(0, 1)
|
|
380
|
+
logPadding.Parent = logLabel
|
|
381
|
+
|
|
382
|
+
logLabel.LayoutOrder = self.logsCount
|
|
383
|
+
logLabel.Parent = self.scrollingFrame
|
|
384
|
+
self.logsCount = self.logsCount + 1
|
|
385
|
+
|
|
386
|
+
-- Limit max logs to prevent memory bloat
|
|
387
|
+
if self.logsCount > 150 then
|
|
388
|
+
local first = self.scrollingFrame:FindFirstChildWhichIsA("TextLabel")
|
|
389
|
+
if first then first:Destroy() end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
-- Update canvas size after a frame so AutomaticSize has resolved
|
|
393
|
+
task.defer(function()
|
|
394
|
+
self.scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, self.listLayout.AbsoluteContentSize.Y + 4)
|
|
395
|
+
self.scrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, self.listLayout.AbsoluteContentSize.Y - self.scrollingFrame.AbsoluteSize.Y))
|
|
396
|
+
end)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
-- ═══════════════════════════════════════════
|
|
400
|
+
-- 3 State Setter (Disconnected, Waiting, Connected)
|
|
401
|
+
-- ═══════════════════════════════════════════
|
|
402
|
+
function UIManager:setStatus(stateStr)
|
|
403
|
+
if stateStr == "disconnected" then
|
|
404
|
+
self.connectBtn.Text = " Connect"
|
|
405
|
+
self._updateBtnBaseColor(Color3.fromRGB(40, 160, 80))
|
|
406
|
+
self.statusLabel.Text = "● Disconnected"
|
|
407
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
|
|
408
|
+
self.termStroke.Color = Color3.fromRGB(40, 40, 48)
|
|
409
|
+
|
|
410
|
+
elseif stateStr == "waiting" then
|
|
411
|
+
self.connectBtn.Text = " Disconnect"
|
|
412
|
+
self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
|
|
413
|
+
self.statusLabel.Text = "● Waiting for AI..."
|
|
414
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(255, 200, 80)
|
|
415
|
+
self.termStroke.Color = Color3.fromRGB(130, 110, 50)
|
|
416
|
+
|
|
417
|
+
elseif stateStr == "connected" then
|
|
418
|
+
self.connectBtn.Text = " Disconnect"
|
|
419
|
+
self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
|
|
420
|
+
self.statusLabel.Text = "● Connected"
|
|
421
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(100, 230, 120)
|
|
422
|
+
self.termStroke.Color = Color3.fromRGB(50, 120, 60)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
-- Mengubah visual custom checkbox
|
|
427
|
+
function UIManager:setAutoConnectState(isAuto)
|
|
428
|
+
self.checkboxInner.Visible = isAuto
|
|
429
|
+
if isAuto then
|
|
430
|
+
self.checkboxStroke.Color = Color3.fromRGB(80, 170, 255)
|
|
431
|
+
else
|
|
432
|
+
self.checkboxStroke.Color = Color3.fromRGB(80, 80, 95)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
-- Sakelar visibilitas panel GUI
|
|
437
|
+
function UIManager:toggleVisibility()
|
|
438
|
+
if self.widget then
|
|
439
|
+
self.widget.Enabled = not self.widget.Enabled
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
return UIManager
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
return function(targetInstance, data, ctx, targetPath)
|
|
2
|
+
if targetInstance:IsA("LuaSourceContainer") then
|
|
3
|
+
local newCode = data or ""
|
|
4
|
+
|
|
5
|
+
-- Catatan: Validasi AST menggunakan loadstring() sengaja DIHAPUS.
|
|
6
|
+
-- Karena secara default Roblox Studio mematikan LoadStringEnabled, menggunakannya
|
|
7
|
+
-- hanya akan membuat AI gagal terus menerus untuk menulis script.
|
|
8
|
+
-- Error syntax akan ditangkap secara native oleh Roblox Output.
|
|
9
|
+
|
|
10
|
+
ctx.scriptHistory[targetPath] = targetInstance.Source
|
|
11
|
+
targetInstance.Source = newCode
|
|
12
|
+
return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil diperbarui." }
|
|
13
|
+
else
|
|
14
|
+
return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
|
|
15
|
+
end
|
|
16
|
+
end
|