> [!WARNING] **Deprecated** > This has been integrated into ab1 by default (and updated since this package's publishing), so there is no need to load this separately. - Published as `abConsole` and ab1.bot - Without input published as `abLogV2` on auxplayer & ab1, and as `pLog` on ab1 - Published as ask on both - Replacing ab-1 function ```javascript {"version":1,"state":{"f43c7d3b-cadd-47be-aa37-62089332623f":{"id":"f43c7d3b-cadd-47be-aa37-62089332623f","space":"shared","tags":{"App.css":":root {\n --background: #f8f9fa;\n --on-background: #212529;\n}\n/* Dark mode support */\n@media (prefers-color-scheme: dark) {\n :root {\n --background: #212529;\n --on-background: #f8f9fa;\n }\n}\n\n.ab-console {\n width: 100%;\n height: var(--app-height);\n display: flex;\n flex-direction: column;\n padding: 0 1rem;\n color: var(--on-background);\n background-color: transparent;\n backdrop-filter: blur(10px) brightness(200%);\n animation-name: slide-app-in;\n animation-duration: 0.5s;\n overflow-y: hidden;\n}\n\n.ab-console::after {\n content: \"\";\n background-color: var(--background);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n opacity: 0.9;\n z-index: 0;\n}\n\n@keyframes slide-app-in {\n from {\n animation-timing-function: cubic-bezier(.6,0,.79,1.03);\n /* transform: translateY(110%); */\n height: 0px;\n min-height: 0px;\n }\n\n to {\n height: var(--app-height);\n min-height: var(--app-height);\n }\n}\n\n.ab-console > div {\n z-index: 1;\n position: relative\n}\n\n.ab-console-log {\n overflow-y: auto;\n flex-grow: 1;\n display: flex;\n flex-direction: column-reverse;\n}\n\n.ab-console-message {\n margin-bottom: 1rem;\n border-radius: 0.25rem;\n background-color: var(--background);\n color: var(--on-background);\n padding: 0.5rem 1rem;\n}\n\n.ab-console-timestamp {\n opacity: 0.6;\n}\n\n.ab-console-input {\n background-color: var(--background);\n color: var(--on-background);\n width: 100%;\n resize: vertical;\n border-radius: 2px;\n padding: 0 0.5rem;\n line-height: 1.8rem;\n\n /* Reset textarea styles */\n border: none;\n outline: none;\n -webkit-appearance: none;\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n}\n\n.ab-console-input:focus {\n border-bottom-width: 2px !important;\n border-bottom-color: #3e82f8 !important;\n border-bottom-style: solid;\n}\n\n.console-send-btn {\n color: var(--on-background);\n background-color: var(--background);\n\tborder: none;\n\tfont: inherit;\n\tcursor: pointer;\n\toutline: inherit;\n padding: 0 1rem;\n}\n\n.console-send-btn > span {\n transform: translateY(3px)\n}","ConsoleInput":"@const { useState, useEffect } = os.appHooks\n\nconst ConsoleInput = () => {\n const [userInput, setUserInput] = useState('')\n const [renderNumber, setRenderNumber] = useState(0)\n\n useEffect(() => {\n if (userInput.length == 0) setRenderNumber(n => n + 1)\n }, [userInput])\n\n const handleChangeInput = (e) => {\n setUserInput(e.target.value)\n }\n\n const handleSend = () => {\n setUserInput('')\n thisBot.masks.userInput = `@try{const result = (${userInput})\n if (typeof result == 'object') return JSON.stringify(result)\n return result} catch (e) {return JSON.stringify(e)}`\n const response = thisBot.userInput()\n whisper(thisBot, 'log', String(response))\n }\n\n return(\n <div style={{display: 'flex', flexDirection: 'row'}}>\n <span style={{marginRight: '0.5rem'}}>{`>`}</span>\n <textarea\n className=\"ab-console-input\"\n placeholder=\"Type here...\"\n id={`ab-console-input`}\n key={`console-${renderNumber}`}\n onKeyDown={handleChangeInput}\n onBlur={handleChangeInput}\n >{userInput}</textarea>\n <button className=\"console-send-btn\" style={{marginLeft: '0.5rem'}}>\n <span className=\"material-icons\" onClick={handleSend}>\n send\n </span>\n </button>\n </div>\n )\n}\n\nreturn ConsoleInput","ResizeHandle":"@const { useEffect, useState, useRef } = os.appHooks\nconst offset = 8\nconst initialHeight = 256\nconst dragInterval = 25\n\nconst ResizeHandle = () => {\n const [height, setHeight] = useState(initialHeight + offset)\n const [listening, setListening] = useState(false)\n\n const updateHeight = () => {\n if (listening) {\n setHeight(gridPortalBot.tags.pointerPixelY + 6 + Math.random()/1000)\n }\n }\n\n useEffect(() => {\n if (listening) {\n updateHeight()\n }\n }, [listening])\n\n useEffect(() => {\n if (height < 80) {\n setListening(false)\n gridPortalBot.tags.portalZoomable = true\n shout('closeAbConsole')\n }\n os.sleep(dragInterval).then(() => {\n updateHeight()\n })\n }, [height])\n\n\n return (<>\n <style>{`:root {--app-height: ${height}px}`}</style>\n <div\n onPointerDown={() => setListening(true)}\n onPointerUp={() => setListening(false)}\n style={{\n width: '100%',\n height: '24px',\n fontSize: '24px',\n textAlign: 'center',\n cursor: 'grab',\n userSelect: 'none',\n }}\n >\n —\n </div>\n </>)\n}\n\nreturn ResizeHandle\n","abLogV2":"true","closeAbConsole":"@var currentVer = masks.consoleVersion ?? 0\nawait os.unregisterApp(`ab-console-${currentVer}`)","getApp":"@const { useEffect, useState } = os.appHooks\nconst ConsoleInput = thisBot.ConsoleInput()\nconst ResizeHandle = thisBot.ResizeHandle()\n\nconst Message = ({timestamp, message}) => {\n return (\n <div className=\"ab-console-message\">\n <div className=\"ab-console-timestamp\">\n {timestamp.toLocaleString('en-US', { timeStyle: 'medium' })}\n </div>\n <div className=\"ab-console-content\">{message}</div>\n </div>\n )\n}\n\nconst App = ({ }) => {\n const [consoleLog, setConsoleLog] = useState(thisBot.masks.history)\n\n const updateLog = () => {\n setConsoleLog([ ...thisBot.masks.history ])\n }\n\n useEffect(() => {\n thisBot.vars.updateLog = updateLog\n return (() => {\n thisBot.vars.updateLog = null\n })\n }, [])\n\n return (<>\n <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\" />\n <style>{tags['App.css']}</style>\n\n <div\n id=\"ab-console\"\n className=\"ab-console\"\n onPointerEnter={() => gridPortalBot.tags.portalZoomable = false}\n onPointerLeave={(e) => {\n if (e.currentTarget.id == \"ab-console\"){\n gridPortalBot.tags.portalZoomable = true\n }\n }}\n >\n\n <div className=\"ab-console-log\">\n {/* Message history */ Array.isArray(consoleLog)\n ? consoleLog.map(m =>\n <Message timestamp={m.timestamp} message={m.message} />)\n : <></>}\n </div>\n\n <ConsoleInput />\n <ResizeHandle />\n </div>\n </>)\n}\n\nreturn App\n","log":"@const message = that\n\n// Create local copy of history mask\nvar _history = thisBot.masks.history\n\n// Push message if the mask existed\nif ( Array.isArray(_history) ) {\n _history.unshift({\n timestamp: new Date(),\n message\n })\n\n// Initialize the array if it didn't\n} else {\n _history = [{\n timestamp: new Date(),\n message\n }]\n}\n\nthisBot.masks.history = [ ..._history ]\nif (thisBot.vars.updateLog) thisBot.vars.updateLog()","onCreate":"@// Replace the regular ab log function\nsetTagMask(ab, \"log\", `@whisper(getBot(\"system\", \"ab.core.console\"), \"log\", that)`, \"local\")\n\nvar inputBot = getBot(\"system\", \"ab.shell.input\")\nvar _onChat = inputBot.tags.onChat\n// Find the start of the .log function (opens the ab log)\nconst firstSplit = _onChat.split(\"case '.log':\")\nconst secondSplit = firstSplit[1].split(/break(.*)/s)\n\nconst newOnChat = firstSplit[0] + \"case '.log':\\n\"\n + ` shout(\"openAbConsole\")\n break`\n + secondSplit[1]\n\nsetTagMask(inputBot, \"onChat\", newOnChat, \"local\")","openAbConsole":"@configBot.masks.tagPortal = null\nconfigBot.masks.tagPortalSpace = null\n\n// Unregister in case it was already open\nvar currentVer = masks.consoleVersion ?? 0\nawait os.unregisterApp(`ab-console-${currentVer}`)\n\ncurrentVer += 1\nmasks.consoleVersion = currentVer\n// Get, register, and compile the app\nconst App = thisBot.getApp()\nawait os.registerApp(`ab-console-${currentVer}`, thisBot)\nos.compileApp(`ab-console-${currentVer}`, <App />)\n","system":"ab.core.console","abIDOrigin":"abLogV2"}}}} ```