Quick actions

cmd+k|ctrl+k

Navigation

Languages

AI Chatbot v1

Snippet info

Language

Lua

Visibility

public

Author

panickyhospital950

Created

2026-04-04T09:51:44.136463Z

Updated

2026-04-04T10:14:20.049065Z

--[[
    AI CHATBOT v1 | In-Game AI Chat for Roblox
    
    Features:
      - Clean modern chat UI (draggable, resizable)
      - AI responses via Groq AI
      - Conversation memory (remembers context)
      - Smart fallback bot if API is down
      - Typing indicator animation
      - Chat history scroll
      - Mobile support
      - Press / to open, ESC to close
      
    Uses: Groq AI
]]

------------------------------------------------------------------------
-- CLEANUP
------------------------------------------------------------------------
pcall(function()
    for _, g in pairs(game:GetService("Players").LocalPlayer.PlayerGui:GetChildren()) do
        if g.Name == "AIChatGui" then g:Destroy() end
    end
end)
pcall(function()
    if _G._AIChatConns then
        for _, c in pairs(_G._AIChatConns) do pcall(function() c:Disconnect() end) end
    end
end)
_G._AIChatConns = {}
local function conn(c) table.insert(_G._AIChatConns, c) return c end

------------------------------------------------------------------------
-- SERVICES
------------------------------------------------------------------------
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local TS = game:GetService("TweenService")
local HttpService = game:GetService("HttpService")
local LP = Players.LocalPlayer

------------------------------------------------------------------------
-- CONFIG
------------------------------------------------------------------------
local CONFIG = {
    -- API endpoints (tried in order, first working one is used)
    apis = {
        {
            url = "https://api.groq.com/openai/v1/chat/completions",
            model = "llama-3.3-70b-versatile",
            key = "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"
        },

    },
    systemPrompt = "You are a friendly and helpful AI assistant chatting inside a Roblox game. Keep responses concise (under 200 words). Be fun, casual, and helpful. You can help with game tips, coding, homework, jokes, stories, or just chat. Use emojis occasionally. When generating code, always wrap it in ```lua code blocks so the user can execute it directly.",
    maxHistory = 20,       -- messages to keep in context
    maxResponseLen = 500,  -- chars
}

------------------------------------------------------------------------
-- COLORS
------------------------------------------------------------------------
local C = {
    bg       = Color3.fromRGB(15, 15, 25),
    chatBg   = Color3.fromRGB(22, 22, 35),
    inputBg  = Color3.fromRGB(30, 30, 48),
    userBub  = Color3.fromRGB(75, 45, 180),
    aiBub    = Color3.fromRGB(35, 38, 55),
    accent   = Color3.fromRGB(130, 80, 255),
    accent2  = Color3.fromRGB(80, 200, 255),
    white    = Color3.fromRGB(230, 230, 245),
    gray     = Color3.fromRGB(140, 140, 165),
    dimgray  = Color3.fromRGB(80, 80, 105),
    green    = Color3.fromRGB(80, 255, 140),
    red      = Color3.fromRGB(255, 80, 80),
    yellow   = Color3.fromRGB(255, 220, 80),
    border   = Color3.fromRGB(60, 40, 130),
    shadow   = Color3.fromRGB(8, 8, 15),
}

------------------------------------------------------------------------
-- STATE
------------------------------------------------------------------------
local chatOpen = false
local isTyping = false
local conversation = {} -- {role="user"/"assistant", content="..."}
local messageFrames = {}
local httpRequest = (syn and syn.request) or (http and http.request) or http_request or request or (fluxus and fluxus.request) or nil

------------------------------------------------------------------------
-- GUI SETUP
------------------------------------------------------------------------
local gui = Instance.new("ScreenGui")
gui.Name = "AIChatGui"
gui.ResetOnSpawn = false
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.DisplayOrder = 100
gui.Parent = LP.PlayerGui

-- Main container
local main = Instance.new("Frame")
main.Name = "Main"
main.Size = UDim2.new(0, 380, 0, 500)
main.Position = UDim2.new(0.5, -190, 0.5, -250)
main.BackgroundColor3 = C.bg
main.BorderSizePixel = 0
main.Visible = false
main.Active = true
main.Draggable = true
main.ClipsDescendants = true
main.Parent = gui
Instance.new("UICorner", main).CornerRadius = UDim.new(0, 14)
local mainStroke = Instance.new("UIStroke", main)
mainStroke.Color = C.border
mainStroke.Thickness = 2
mainStroke.Transparency = 0.3

-- Drop shadow (fake)
local shadow = Instance.new("Frame")
shadow.Size = UDim2.new(1, 16, 1, 16)
shadow.Position = UDim2.new(0, -8, 0, -8)
shadow.BackgroundColor3 = C.shadow
shadow.BackgroundTransparency = 0.6
shadow.BorderSizePixel = 0
shadow.ZIndex = -1
shadow.Parent = main
Instance.new("UICorner", shadow).CornerRadius = UDim.new(0, 18)

------------------------------------------------------------------------
-- HEADER
------------------------------------------------------------------------
local header = Instance.new("Frame")
header.Size = UDim2.new(1, 0, 0, 50)
header.BackgroundColor3 = Color3.fromRGB(25, 20, 45)
header.BorderSizePixel = 0
header.ZIndex = 5
header.Parent = main
Instance.new("UICorner", header).CornerRadius = UDim.new(0, 14)

local headerFix = Instance.new("Frame")
headerFix.Size = UDim2.new(1, 0, 0, 14)
headerFix.Position = UDim2.new(0, 0, 1, -14)
headerFix.BackgroundColor3 = Color3.fromRGB(25, 20, 45)
headerFix.BorderSizePixel = 0
headerFix.ZIndex = 5
headerFix.Parent = header

-- AI icon (circle)
local aiIcon = Instance.new("Frame")
aiIcon.Size = UDim2.new(0, 32, 0, 32)
aiIcon.Position = UDim2.new(0, 12, 0.5, -16)
aiIcon.BackgroundColor3 = C.accent
aiIcon.BorderSizePixel = 0
aiIcon.ZIndex = 6
aiIcon.Parent = header
Instance.new("UICorner", aiIcon).CornerRadius = UDim.new(1, 0)

local aiIconTxt = Instance.new("TextLabel")
aiIconTxt.Size = UDim2.new(1, 0, 1, 0)
aiIconTxt.BackgroundTransparency = 1
aiIconTxt.Text = "AI"
aiIconTxt.TextColor3 = C.white
aiIconTxt.Font = Enum.Font.GothamBold
aiIconTxt.TextSize = 13
aiIconTxt.ZIndex = 7
aiIconTxt.Parent = aiIcon

-- Title
local titleLbl = Instance.new("TextLabel")
titleLbl.Size = UDim2.new(0, 200, 0, 20)
titleLbl.Position = UDim2.new(0, 52, 0, 7)
titleLbl.BackgroundTransparency = 1
titleLbl.Text = "šŸ¤– AI Chat"
titleLbl.TextColor3 = C.white
titleLbl.Font = Enum.Font.GothamBold
titleLbl.TextSize = 16
titleLbl.TextXAlignment = Enum.TextXAlignment.Left
titleLbl.ZIndex = 6
titleLbl.Parent = header

-- Status
local statusLbl = Instance.new("TextLabel")
statusLbl.Size = UDim2.new(0, 200, 0, 14)
statusLbl.Position = UDim2.new(0, 52, 0, 28)
statusLbl.BackgroundTransparency = 1
statusLbl.Text = "Online • Type a message"
statusLbl.TextColor3 = C.green
statusLbl.Font = Enum.Font.Gotham
statusLbl.TextSize = 11
statusLbl.TextXAlignment = Enum.TextXAlignment.Left
statusLbl.ZIndex = 6
statusLbl.Parent = header

-- Close button
local closeBtn = Instance.new("TextButton")
closeBtn.Size = UDim2.new(0, 36, 0, 36)
closeBtn.Position = UDim2.new(1, -44, 0.5, -18)
closeBtn.BackgroundColor3 = C.red
closeBtn.BackgroundTransparency = 0.7
closeBtn.BorderSizePixel = 0
closeBtn.Text = "āœ•"
closeBtn.TextColor3 = C.white
closeBtn.Font = Enum.Font.GothamBold
closeBtn.TextSize = 16
closeBtn.ZIndex = 6
closeBtn.AutoButtonColor = false
closeBtn.Parent = header
Instance.new("UICorner", closeBtn).CornerRadius = UDim.new(0, 8)

closeBtn.MouseEnter:Connect(function()
    TS:Create(closeBtn, TweenInfo.new(0.15), {BackgroundTransparency = 0.3}):Play()
end)
closeBtn.MouseLeave:Connect(function()
    TS:Create(closeBtn, TweenInfo.new(0.15), {BackgroundTransparency = 0.7}):Play()
end)

-- Clear button
local clearBtn = Instance.new("TextButton")
clearBtn.Size = UDim2.new(0, 36, 0, 36)
clearBtn.Position = UDim2.new(1, -84, 0.5, -18)
clearBtn.BackgroundColor3 = C.dimgray
clearBtn.BackgroundTransparency = 0.7
clearBtn.BorderSizePixel = 0
clearBtn.Text = "šŸ—‘"
clearBtn.TextColor3 = C.white
clearBtn.Font = Enum.Font.GothamBold
clearBtn.TextSize = 14
clearBtn.ZIndex = 6
clearBtn.AutoButtonColor = false
clearBtn.Parent = header
Instance.new("UICorner", clearBtn).CornerRadius = UDim.new(0, 8)

------------------------------------------------------------------------
-- CHAT AREA (scrolling)
------------------------------------------------------------------------
local chatArea = Instance.new("ScrollingFrame")
chatArea.Size = UDim2.new(1, -16, 1, -114)
chatArea.Position = UDim2.new(0, 8, 0, 54)
chatArea.BackgroundTransparency = 1
chatArea.BorderSizePixel = 0
chatArea.ScrollBarThickness = 3
chatArea.ScrollBarImageColor3 = C.accent
chatArea.CanvasSize = UDim2.new(0, 0, 0, 0)
chatArea.AutomaticCanvasSize = Enum.AutomaticSize.Y
chatArea.ScrollingDirection = Enum.ScrollingDirection.Y
chatArea.Parent = main

local chatLayout = Instance.new("UIListLayout", chatArea)
chatLayout.Padding = UDim.new(0, 8)
chatLayout.SortOrder = Enum.SortOrder.LayoutOrder
chatLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center

local chatPadding = Instance.new("UIPadding", chatArea)
chatPadding.PaddingTop = UDim.new(0, 8)
chatPadding.PaddingBottom = UDim.new(0, 8)

------------------------------------------------------------------------
-- INPUT BAR
------------------------------------------------------------------------
local inputBar = Instance.new("Frame")
inputBar.Size = UDim2.new(1, -16, 0, 48)
inputBar.Position = UDim2.new(0, 8, 1, -56)
inputBar.BackgroundColor3 = C.inputBg
inputBar.BorderSizePixel = 0
inputBar.ZIndex = 5
inputBar.Parent = main
Instance.new("UICorner", inputBar).CornerRadius = UDim.new(0, 12)
Instance.new("UIStroke", inputBar).Color = C.border

local inputBox = Instance.new("TextBox")
inputBox.Size = UDim2.new(1, -56, 1, -12)
inputBox.Position = UDim2.new(0, 14, 0, 6)
inputBox.BackgroundTransparency = 1
inputBox.Text = ""
inputBox.PlaceholderText = "Type a message..."
inputBox.PlaceholderColor3 = C.dimgray
inputBox.TextColor3 = C.white
inputBox.Font = Enum.Font.Gotham
inputBox.TextSize = 14
inputBox.TextXAlignment = Enum.TextXAlignment.Left
inputBox.ClearTextOnFocus = false
inputBox.ZIndex = 6
inputBox.Parent = inputBar

local sendBtn = Instance.new("TextButton")
sendBtn.Size = UDim2.new(0, 36, 0, 36)
sendBtn.Position = UDim2.new(1, -42, 0.5, -18)
sendBtn.BackgroundColor3 = C.accent
sendBtn.BorderSizePixel = 0
sendBtn.Text = "šŸ“Ø"
sendBtn.TextColor3 = C.white
sendBtn.Font = Enum.Font.GothamBold
sendBtn.TextSize = 18
sendBtn.ZIndex = 6
sendBtn.AutoButtonColor = false
sendBtn.Parent = inputBar
Instance.new("UICorner", sendBtn).CornerRadius = UDim.new(0, 10)

------------------------------------------------------------------------
-- MESSAGE BUBBLE FACTORY
------------------------------------------------------------------------
local msgOrder = 0

local function scrollToBottom()
    task.defer(function()
        task.wait(0.05)
        chatArea.CanvasPosition = Vector2.new(0, chatArea.AbsoluteCanvasSize.Y)
    end)
end

local function addMessage(text, role, animate)
    msgOrder = msgOrder + 1
    local isUser = (role == "user")
    
    -- Container
    local container = Instance.new("Frame")
    container.Size = UDim2.new(1, -8, 0, 0)
    container.AutomaticSize = Enum.AutomaticSize.Y
    container.BackgroundTransparency = 1
    container.LayoutOrder = msgOrder
    container.Parent = chatArea
    
    -- Bubble
    local bubble = Instance.new("Frame")
    bubble.Size = UDim2.new(0.85, 0, 0, 0)
    bubble.AutomaticSize = Enum.AutomaticSize.Y
    bubble.Position = isUser and UDim2.new(0.15, 0, 0, 0) or UDim2.new(0, 0, 0, 0)
    bubble.BackgroundColor3 = isUser and C.userBub or C.aiBub
    bubble.BorderSizePixel = 0
    bubble.Parent = container
    Instance.new("UICorner", bubble).CornerRadius = UDim.new(0, 12)
    
    local bLayout = Instance.new("UIListLayout", bubble)
    bLayout.SortOrder = Enum.SortOrder.LayoutOrder
    bLayout.Padding = UDim.new(0, 4)
    
    local padding = Instance.new("UIPadding", bubble)
    padding.PaddingTop = UDim.new(0, 10)
    padding.PaddingBottom = UDim.new(0, 10)
    padding.PaddingLeft = UDim.new(0, 12)
    padding.PaddingRight = UDim.new(0, 12)
    
    -- Role label with icons
    local roleLbl = Instance.new("TextLabel")
    roleLbl.Size = UDim2.new(1, 0, 0, 16)
    roleLbl.BackgroundTransparency = 1
    roleLbl.Text = isUser and "šŸ‘¤ You" or "šŸ¤– AI Assistant"
    roleLbl.TextColor3 = isUser and C.accent2 or C.green
    roleLbl.Font = Enum.Font.GothamBold
    roleLbl.TextSize = 11
    roleLbl.TextXAlignment = isUser and Enum.TextXAlignment.Right or Enum.TextXAlignment.Left
    roleLbl.LayoutOrder = 1
    roleLbl.Parent = bubble
    
    -- Message text
    local msgLbl = Instance.new("TextLabel")
    msgLbl.Name = "MsgLabel"
    msgLbl.Size = UDim2.new(1, 0, 0, 0)
    msgLbl.AutomaticSize = Enum.AutomaticSize.Y
    msgLbl.BackgroundTransparency = 1
    msgLbl.Text = text
    msgLbl.TextColor3 = C.white
    msgLbl.Font = Enum.Font.Gotham
    msgLbl.TextSize = 13
    msgLbl.TextWrapped = true
    msgLbl.TextXAlignment = Enum.TextXAlignment.Left
    msgLbl.RichText = true
    msgLbl.LayoutOrder = 2
    msgLbl.Parent = bubble
    
    -- Animation
    if animate then
        bubble.BackgroundTransparency = 1
        msgLbl.TextTransparency = 1
        roleLbl.TextTransparency = 1
        
        TS:Create(bubble, TweenInfo.new(0.25, Enum.EasingStyle.Quad), {BackgroundTransparency = 0}):Play()
        TS:Create(msgLbl, TweenInfo.new(0.25, Enum.EasingStyle.Quad), {TextTransparency = 0}):Play()
        TS:Create(roleLbl, TweenInfo.new(0.25, Enum.EasingStyle.Quad), {TextTransparency = 0}):Play()
    end
    
    table.insert(messageFrames, container)
    scrollToBottom()
    
    return msgLbl, bubble -- return both for code box rendering
end


-- Typing indicator
local typingContainer = nil
local typingDots = {}

local function showTyping()
    if typingContainer then return end
    
    msgOrder = msgOrder + 1
    typingContainer = Instance.new("Frame")
    typingContainer.Size = UDim2.new(1, -8, 0, 36)
    typingContainer.BackgroundTransparency = 1
    typingContainer.LayoutOrder = msgOrder + 9999
    typingContainer.Parent = chatArea
    
    local bubble = Instance.new("Frame")
    bubble.Size = UDim2.new(0, 80, 0, 32)
    bubble.BackgroundColor3 = C.aiBub
    bubble.BorderSizePixel = 0
    bubble.Parent = typingContainer
    Instance.new("UICorner", bubble).CornerRadius = UDim.new(0, 12)
    
    for i = 1, 3 do
        local dot = Instance.new("Frame")
        dot.Size = UDim2.new(0, 8, 0, 8)
        dot.Position = UDim2.new(0, 16 + (i - 1) * 18, 0.5, -4)
        dot.BackgroundColor3 = C.gray
        dot.BorderSizePixel = 0
        dot.Parent = bubble
        Instance.new("UICorner", dot).CornerRadius = UDim.new(1, 0)
        table.insert(typingDots, dot)
    end
    
    -- Animate dots
    task.spawn(function()
        local idx = 0
        while typingContainer and typingContainer.Parent do
            idx = idx + 1
            for i, dot in pairs(typingDots) do
                local active = ((idx + i) % 3 == 0)
                TS:Create(dot, TweenInfo.new(0.3), {
                    BackgroundColor3 = active and C.accent or C.gray,
                    Size = active and UDim2.new(0, 10, 0, 10) or UDim2.new(0, 8, 0, 8),
                    Position = active and UDim2.new(0, 15 + (i-1)*18, 0.5, -5) or UDim2.new(0, 16 + (i-1)*18, 0.5, -4),
                }):Play()
            end
            task.wait(0.4)
        end
    end)
    
    scrollToBottom()
end

local function hideTyping()
    if typingContainer then
        typingContainer:Destroy()
        typingContainer = nil
        typingDots = {}
    end
end

------------------------------------------------------------------------
-- SMART FALLBACK BOT (when API unavailable)
------------------------------------------------------------------------
local function fallbackResponse(msg)
    local low = msg:lower()
    
    -- Greetings
    if low:find("^h[ie]") or low:find("hello") or low:find("hey") or low:find("sup") or low:find("yo ") or low == "yo" then
        local greetings = {
            "Hey there! šŸ‘‹ What's up?",
            "Hello! How's it going? šŸ˜„",
            "Hey! What can I help you with?",
            "Yo! What's on your mind? šŸŽ®",
            "Hi there! Ready to chat! ✨",
        }
        return greetings[math.random(#greetings)]
    end
    
    -- How are you
    if low:find("how are you") or low:find("how r u") or low:find("hru") or low:find("wyd") then
        local responses = {
            "I'm doing great! Just vibing in Roblox šŸ˜Ž How about you?",
            "Living my best AI life! What about you? šŸ¤–",
            "Pretty good! Ready to help with whatever you need! ✨",
        }
        return responses[math.random(#responses)]
    end
    
    -- Jokes
    if low:find("joke") or low:find("funny") or low:find("laugh") then
        local jokes = {
            "Why do programmers prefer dark mode? Because light attracts bugs! šŸ›šŸ˜‚",
            "Why did the Roblox player cross the road? To get to the other server! šŸ˜„",
            "What's a noob's favorite key? The respawn button! šŸ’€šŸ˜‚",
            "Why don't scientists trust atoms? Because they make up everything! āš›ļøšŸ˜‚",
            "I told my computer I needed a break... now it won't stop sending me Kit-Kat ads šŸ«",
            "Why was the JavaScript developer sad? Because he didn't Node how to Express himself! šŸ’»",
            "What do you call a fake noodle? An impasta! šŸšŸ˜‚",
            "Why did the scarecrow win an award? He was outstanding in his field! 🌾",
        }
        return jokes[math.random(#jokes)]
    end
    
    -- Help
    if low:find("help") or low:find("what can you") or low:find("what do you") then
        return "I can help with lots of things! šŸŽÆ\n\n• Chat & have fun conversations\n• Tell jokes šŸ˜‚\n• Help with game tips\n• Answer questions\n• Help with homework/coding\n• Tell stories\n• Play word games\n\nJust ask me anything! (Note: I'm running in fallback mode since the AI API isn't connected. Add your API key for full AI responses!)"
    end
    
    -- Math
    if low:find("what is %d") or low:find("calculate") or low:find("%d+%s*[%+%-%*/]%s*%d+") then
        local expr = low:match("(%d+%s*[%+%-%*/]%s*%d+)")
        if expr then
            local fn = loadstring("return " .. expr)
            if fn then
                local ok, result = pcall(fn)
                if ok then
                    return "🧮 " .. expr .. " = **" .. tostring(result) .. "**"
                end
            end
        end
        return "I can do basic math! Try something like '2 + 2' or '15 * 3' 🧮"
    end
    
    -- Games
    if low:find("game") or low:find("roblox") or low:find("play") then
        local responses = {
            "Roblox is awesome! What game are you playing right now? šŸŽ®",
            "There are so many great games on Roblox! Some favorites are Blox Fruits, Arsenal, and Adopt Me! What do you like? šŸ•¹ļø",
            "Gaming is life! šŸŽ® Are you looking for game recommendations or tips?",
        }
        return responses[math.random(#responses)]
    end
    
    -- Compliments
    if low:find("thank") or low:find("cool") or low:find("awesome") or low:find("nice") or low:find("good bot") then
        local responses = {
            "Aww thanks! You're awesome too! 😊✨",
            "Glad I could help! šŸŽ‰",
            "You're too kind! šŸ’œ",
            "Thanks! That means a lot coming from you! šŸ˜„",
        }
        return responses[math.random(#responses)]
    end
    
    -- Story
    if low:find("story") or low:find("tell me") then
        return "šŸ“– Once upon a time, in a blocky world made of cubes, there lived a brave Robloxian named " .. LP.Name .. ". They ventured through dangerous obbies, battled fierce noobs, and collected legendary items. One day, they discovered a mysterious portal that led to... well, that's a story for another time! šŸ˜‰āœØ"
    end
    
    -- Who are you
    if low:find("who are you") or low:find("what are you") or low:find("your name") then
        return "I'm an AI chatbot running right inside your Roblox game! šŸ¤–āœØ I'm here to chat, help out, tell jokes, or just hang out. Think of me as your in-game AI buddy!"
    end
    
    -- Bye
    if low:find("bye") or low:find("gtg") or low:find("gotta go") or low:find("cya") or low:find("see ya") then
        local responses = {
            "See ya later! Have fun! šŸ‘‹šŸ˜„",
            "Bye! Come chat anytime! āœŒļø",
            "Later! Stay awesome! šŸŽ®āœØ",
        }
        return responses[math.random(#responses)]
    end
    
    -- Default responses
    local defaults = {
        "That's interesting! Tell me more about that šŸ¤”",
        "Hmm, I'm thinking about that... Could you elaborate? šŸ’­",
        "Cool! What else is on your mind? 😊",
        "I hear you! Anything else you want to chat about? šŸŽÆ",
        "That's a great point! šŸ‘ What do you think about it?",
        "Interesting! I'm in fallback mode right now, but I'm still here to chat! Add an API key in the script config for full AI responses šŸ¤–",
        "I love chatting! Even without the AI API connected, I can still hang out with you šŸ˜„",
    }
    return defaults[math.random(#defaults)]
end

------------------------------------------------------------------------
-- API CALL
------------------------------------------------------------------------
local usingAPI = false
local apiName = "Fallback"

local function callAI(userMsg)
    -- Build messages array
    local messages = {
        {role = "system", content = CONFIG.systemPrompt},
    }
    
    -- Add conversation history
    local startIdx = math.max(1, #conversation - CONFIG.maxHistory + 1)
    for i = startIdx, #conversation do
        table.insert(messages, conversation[i])
    end
    
    -- Add current message
    table.insert(messages, {role = "user", content = userMsg})
    
    -- Try each API
    if httpRequest then
        for _, api in pairs(CONFIG.apis) do
            do -- try each API
                local ok, result = pcall(function()
                    local body = HttpService:JSONEncode({
                        model = api.model,
                        messages = messages,
                        max_tokens = 300,
                        temperature = 0.8,
                    })
                    
                    local response = httpRequest({
                        Url = api.url,
                        Method = "POST",
                        Headers = {
                            ["Content-Type"] = "application/json",
                            ["Authorization"] = "Bearer " .. api.key,
                        },
                        Body = body,
                    })
                    
                    if response.StatusCode == 200 then
                        local data = HttpService:JSONDecode(response.Body)
                        if data.choices and data.choices[1] then
                            usingAPI = true
                            apiName = api.model
                            return data.choices[1].message.content
                        end
                    end
                    return nil
                end)
                
                if ok and result then
                    return result
                end
            end
        end
    end
    
    -- Fallback
    usingAPI = false
    apiName = "Fallback"
    return fallbackResponse(userMsg)
end

------------------------------------------------------------------------
-- SEND MESSAGE
------------------------------------------------------------------------
local function sendMessage()
    local text = inputBox.Text
    if text == "" or isTyping then return end
    
    text = text:sub(1, 500) -- limit
    inputBox.Text = ""
    
    -- Add user message
    table.insert(conversation, {role = "user", content = text})
    addMessage(text, "user", true)
    
    -- Show typing
    isTyping = true
    statusLbl.Text = "🧠 AI is thinking..."
    statusLbl.TextColor3 = C.yellow
    showTyping()
    
    -- Get response async
    task.spawn(function()
        local response = callAI(text)
        
        -- Trim response
        if #response > CONFIG.maxResponseLen then
            response = response:sub(1, CONFIG.maxResponseLen) .. "..."
        end
        
        -- Store in conversation
        table.insert(conversation, {role = "assistant", content = response})
        
        -- Trim conversation history
        while #conversation > CONFIG.maxHistory * 2 do
            table.remove(conversation, 1)
        end
        
        hideTyping()
        
        -- Typewriter effect
        local msgLbl, aiBubble = addMessage("", "assistant", true)
        
        local displayed = ""
        for i = 1, #response do
            displayed = response:sub(1, i)
            msgLbl.Text = displayed
            scrollToBottom()
            
            -- Variable speed for natural feel
            local ch = response:sub(i, i)
            if ch == "." or ch == "!" or ch == "?" then
                task.wait(0.04)
            elseif ch == "," or ch == ":" then
                task.wait(0.02)
            elseif ch == "\n" then
                task.wait(0.03)
            else
                task.wait(0.008)
            end
        end
        
        -- Parse and render code blocks in a styled box with execute button
        local function renderCodeBlocks(fullText, parentBubble)
            -- Find code blocks: ```...```
            local codeBlocks = {}
            for codeContent in fullText:gmatch("```(.-)```") do
                table.insert(codeBlocks, codeContent)
            end
            
            if #codeBlocks == 0 then return end
            
            for _, rawCode in pairs(codeBlocks) do
                -- Clean language tag
                local cleanCode = rawCode:gsub("^%s*[Ll]ua[u]?%s*\n", ""):gsub("^%s*\n", ""):gsub("\n%s*$", "")
                
                -- Code box container
                local codeBox = Instance.new("Frame")
                codeBox.Size = UDim2.new(1, 0, 0, 0)
                codeBox.AutomaticSize = Enum.AutomaticSize.Y
                codeBox.BackgroundColor3 = Color3.fromRGB(15, 15, 25)
                codeBox.BorderSizePixel = 0
                codeBox.LayoutOrder = 3
                codeBox.Parent = parentBubble
                Instance.new("UICorner", codeBox).CornerRadius = UDim.new(0, 8)
                local codeStroke = Instance.new("UIStroke", codeBox)
                codeStroke.Color = Color3.fromRGB(80, 60, 160)
                codeStroke.Thickness = 1
                codeStroke.Transparency = 0.5
                
                local codePad = Instance.new("UIPadding", codeBox)
                codePad.PaddingTop = UDim.new(0, 6)
                codePad.PaddingBottom = UDim.new(0, 6)
                codePad.PaddingLeft = UDim.new(0, 10)
                codePad.PaddingRight = UDim.new(0, 10)
                
                local codeLayout = Instance.new("UIListLayout", codeBox)
                codeLayout.SortOrder = Enum.SortOrder.LayoutOrder
                codeLayout.Padding = UDim.new(0, 4)
                
                -- Code header with icon
                local codeHeader = Instance.new("TextLabel")
                codeHeader.Size = UDim2.new(1, 0, 0, 16)
                codeHeader.BackgroundTransparency = 1
                codeHeader.Text = "šŸ“œ Code"
                codeHeader.TextColor3 = Color3.fromRGB(160, 140, 255)
                codeHeader.Font = Enum.Font.GothamBold
                codeHeader.TextSize = 10
                codeHeader.TextXAlignment = Enum.TextXAlignment.Left
                codeHeader.LayoutOrder = 1
                codeHeader.Parent = codeBox
                
                -- Code text (monospace style)
                local codeLbl = Instance.new("TextLabel")
                codeLbl.Size = UDim2.new(1, 0, 0, 0)
                codeLbl.AutomaticSize = Enum.AutomaticSize.Y
                codeLbl.BackgroundTransparency = 1
                codeLbl.Text = cleanCode
                codeLbl.TextColor3 = Color3.fromRGB(200, 220, 255)
                codeLbl.Font = Enum.Font.Code
                codeLbl.TextSize = 11
                codeLbl.TextWrapped = true
                codeLbl.TextXAlignment = Enum.TextXAlignment.Left
                codeLbl.LayoutOrder = 2
                codeLbl.Parent = codeBox
                
                -- Button row
                local btnRow = Instance.new("Frame")
                btnRow.Size = UDim2.new(1, 0, 0, 30)
                btnRow.BackgroundTransparency = 1
                btnRow.LayoutOrder = 3
                btnRow.Parent = codeBox
                
                local btnLayout = Instance.new("UIListLayout", btnRow)
                btnLayout.FillDirection = Enum.FillDirection.Horizontal
                btnLayout.SortOrder = Enum.SortOrder.LayoutOrder
                btnLayout.Padding = UDim.new(0, 6)
                
                -- Execute button
                local execBtn = Instance.new("TextButton")
                execBtn.Size = UDim2.new(0, 130, 0, 28)
                execBtn.BackgroundColor3 = Color3.fromRGB(40, 167, 69)
                execBtn.BorderSizePixel = 0
                execBtn.Text = "ā–¶ļø Execute Code"
                execBtn.TextColor3 = Color3.new(1, 1, 1)
                execBtn.Font = Enum.Font.GothamBold
                execBtn.TextSize = 11
                execBtn.LayoutOrder = 1
                execBtn.Parent = btnRow
                Instance.new("UICorner", execBtn).CornerRadius = UDim.new(0, 6)
                
                -- Copy button
                local copyBtn = Instance.new("TextButton")
                copyBtn.Size = UDim2.new(0, 90, 0, 28)
                copyBtn.BackgroundColor3 = Color3.fromRGB(60, 50, 120)
                copyBtn.BorderSizePixel = 0
                copyBtn.Text = "šŸ“‹ Copy"
                copyBtn.TextColor3 = Color3.new(1, 1, 1)
                copyBtn.Font = Enum.Font.GothamBold
                copyBtn.TextSize = 11
                copyBtn.LayoutOrder = 2
                copyBtn.Parent = btnRow
                Instance.new("UICorner", copyBtn).CornerRadius = UDim.new(0, 6)
                
                -- Execute handler
                conn(execBtn.MouseButton1Click:Connect(function()
                    execBtn.Text = "ā³ Running..."
                    execBtn.BackgroundColor3 = Color3.fromRGB(255, 193, 7)
                    execBtn.TextColor3 = Color3.fromRGB(30, 30, 30)
                    local ok, err = pcall(function()
                        local fn = loadstring(cleanCode)
                        if fn then fn() else error("Failed to compile") end
                    end)
                    if ok then
                        execBtn.Text = "āœ… Success!"
                        execBtn.BackgroundColor3 = Color3.fromRGB(40, 167, 69)
                        execBtn.TextColor3 = Color3.new(1, 1, 1)
                    else
                        execBtn.Text = "āŒ Error"
                        execBtn.BackgroundColor3 = Color3.fromRGB(220, 53, 69)
                        execBtn.TextColor3 = Color3.new(1, 1, 1)
                        addMessage("āš ļø Error: " .. tostring(err), "assistant", true)
                    end
                    task.delay(3, function()
                        pcall(function()
                            execBtn.Text = "ā–¶ļø Execute Code"
                            execBtn.BackgroundColor3 = Color3.fromRGB(40, 167, 69)
                        end)
                    end)
                end))
                
                -- Copy handler
                conn(copyBtn.MouseButton1Click:Connect(function()
                    pcall(function() setclipboard(cleanCode) end)
                    copyBtn.Text = "āœ… Copied!"
                    task.delay(2, function()
                        pcall(function() copyBtn.Text = "šŸ“‹ Copy" end)
                    end)
                end))
            end
            scrollToBottom()
        end
        
        renderCodeBlocks(response, aiBubble)
        
        isTyping = false
        local modeText = usingAPI and ("Online • " .. apiName) or "🟔 Online • Fallback Mode"
        statusLbl.Text = modeText
        statusLbl.TextColor3 = C.green
    end)
end

------------------------------------------------------------------------
-- WELCOME MESSAGE
------------------------------------------------------------------------
local function showWelcome()
    addMessage("Hey " .. LP.Name .. "! šŸ‘‹ I'm your AI chatbot!\n\nYou can ask me anything — questions, jokes, help with stuff, or just chat!\n\nšŸ’” <b>Tip:</b> Add your API key in the script config for full AI responses. Without it, I use a smart fallback bot.\n\n<b>Press / to open • ESC to close</b>", "assistant", false)
end

------------------------------------------------------------------------
-- CLEAR CHAT
------------------------------------------------------------------------
local function clearChat()
    for _, frame in pairs(messageFrames) do
        pcall(function() frame:Destroy() end)
    end
    messageFrames = {}
    conversation = {}
    msgOrder = 0
    hideTyping()
    showWelcome()
end

------------------------------------------------------------------------
-- BUTTON CONNECTIONS
------------------------------------------------------------------------
closeBtn.MouseButton1Click:Connect(function()
    chatOpen = false
    main.Visible = false
end)

clearBtn.MouseButton1Click:Connect(function()
    clearChat()
end)

sendBtn.MouseButton1Click:Connect(function()
    sendMessage()
end)

inputBox.FocusLost:Connect(function(enter)
    if enter then
        sendMessage()
    end
end)

-- Send button hover
sendBtn.MouseEnter:Connect(function()
    TS:Create(sendBtn, TweenInfo.new(0.15), {BackgroundColor3 = Color3.fromRGB(150, 100, 255)}):Play()
end)
sendBtn.MouseLeave:Connect(function()
    TS:Create(sendBtn, TweenInfo.new(0.15), {BackgroundColor3 = C.accent}):Play()
end)

------------------------------------------------------------------------
-- OPEN/CLOSE HOTKEYS
------------------------------------------------------------------------
conn(UIS.InputBegan:Connect(function(inp, gpe)
    if inp.KeyCode == Enum.KeyCode.Escape and chatOpen then
        chatOpen = false
        main.Visible = false
        return
    end
    
    if gpe then return end
    
    if inp.KeyCode == Enum.KeyCode.Slash then
        chatOpen = not chatOpen
        main.Visible = chatOpen
        if chatOpen then
            task.defer(function()
                inputBox:CaptureFocus()
            end)
        end
    end
end))

------------------------------------------------------------------------
-- MOBILE OPEN BUTTON
------------------------------------------------------------------------
local isMobile = UIS.TouchEnabled and not UIS.KeyboardEnabled

local openBtn = Instance.new("TextButton")
openBtn.Size = UDim2.new(0, 50, 0, 50)
openBtn.Position = isMobile and UDim2.new(1, -60, 0.4, 0) or UDim2.new(1, -60, 0, 10)
openBtn.BackgroundColor3 = C.accent
openBtn.BackgroundTransparency = 0.2
openBtn.BorderSizePixel = 0
openBtn.Text = "šŸ¤–šŸ’¬"
openBtn.TextSize = 24
openBtn.Font = Enum.Font.GothamBold
openBtn.TextColor3 = C.white
openBtn.Active = true
openBtn.Draggable = true
openBtn.Parent = gui
Instance.new("UICorner", openBtn).CornerRadius = UDim.new(0, 25)
Instance.new("UIStroke", openBtn).Color = C.border

-- Notification dot
local notifDot = Instance.new("Frame")
notifDot.Size = UDim2.new(0, 14, 0, 14)
notifDot.Position = UDim2.new(1, -6, 0, -4)
notifDot.BackgroundColor3 = C.green
notifDot.BorderSizePixel = 0
notifDot.Parent = openBtn
Instance.new("UICorner", notifDot).CornerRadius = UDim.new(1, 0)

openBtn.MouseButton1Click:Connect(function()
    chatOpen = not chatOpen
    main.Visible = chatOpen
    if chatOpen then
        notifDot.Visible = false
        if not isMobile then
            task.defer(function() inputBox:CaptureFocus() end)
        end
    end
end)

-- Pulse animation on open button
task.spawn(function()
    while task.wait(2) do
        if not chatOpen then
            TS:Create(openBtn, TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), {
                BackgroundTransparency = 0.5
            }):Play()
            task.wait(0.5)
            TS:Create(openBtn, TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), {
                BackgroundTransparency = 0.2
            }):Play()
        end
    end
end)

------------------------------------------------------------------------
-- ANTI-AFK
------------------------------------------------------------------------
do
    local vu = game:GetService("VirtualUser")
    conn(LP.Idled:Connect(function()
        vu:CaptureController()
        vu:ClickButton2(Vector2.new())
    end))
end

------------------------------------------------------------------------
-- INIT
------------------------------------------------------------------------
showWelcome()

print("=== AI CHATBOT v1 ===")
print("  Press / to open chat")
print("  Press ESC to close")
print("  Or tap the šŸ’¬ button")
if httpRequest then
    print("  HTTP detected! Add API keys in config for full AI.")
else
    print("  No HTTP function found. Using smart fallback bot.")
end
print("=====================")
INFO