Advertisement
MrFinn

ComputerCraft - Glados final

Nov 13th, 2024 (edited)
137
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 29.22 KB | None | 0 0
  1. --[[
  2. ----------------------------------------------------------
  3. GPT.lua - ChatGPT Interface for Computercraft Computers
  4. ----------------------------------------------------------
  5.  
  6. Author: Connor J. Swislow
  7. License: Unlicense (public domain dedication)
  8.  
  9. Description:
  10. GPT.lua is a Lua script designed for Computercraft computers, providing an interface to interact with the OpenAI ChatGPT API. It allows users to have interactive conversations with the language model using a simple chat-based system.
  11.  
  12. Requirements:
  13. - OpenAI API key: To use this program, you need to have an OpenAI API key. Make sure to obtain one before running the script.
  14. - Computercraft: This script is designed to run within the Computercraft mod for Minecraft. Ensure that you have Computercraft installed and running on your Minecraft server or local game.
  15.  
  16. Installation:
  17. 1. Open a Computercraft computer in your Minecraft world.
  18. 2. Run the following command to download the GPT.lua script from Pastebin:
  19. pastebin get Zry5i7qh startup
  20. 3. Restart the Computercraft computer to initialize the program.
  21.  
  22. Usage:
  23. 1. After the program has been installed and the computer has restarted, it will prompt you to enter your OpenAI API key.
  24. 2. Paste your API key when prompted. This step is required to authenticate your access to the OpenAI ChatGPT API.
  25. 3. Once the API key is set, the program will display a randomly chosen title for the chatbot.
  26. 4. Type your messages to have a conversation with the chatbot. Type 'exit' to exit the program.
  27. 5. The chatbot will respond to your messages based on the conversation history and the underlying language model.
  28. 6. Enjoy interacting with the ChatGPT-powered chatbot!
  29.  
  30. Note:
  31. - This script is dependent on an internet connection to communicate with the OpenAI ChatGPT API.
  32. - Use the GPT-3.5 Turbo model for generating responses. Adjust the 'model' variable in the script if you want to use a different model.
  33.  
  34. Credits:
  35. This program is based on the OpenAI GPT-3.5 language model and was created by Connor J. Swislow.
  36.  
  37. Website:
  38. https://github.com/ConnorSwis/GPT.lua
  39.  
  40. ----------------------------------------------------------
  41. --]]
  42.  
  43. local speaker = peripheral.wrap("right")
  44.  
  45. -- Define utility functions for common operations
  46. Utility = {
  47. -- Splits a string into a table by a specified separator
  48. split = function(inputstr, sep)
  49. sep = sep or '%s' -- Default separator is whitespace
  50. local t = {}
  51. for field, s in string.gmatch(inputstr, "([^" .. sep .. "]*)(" .. sep .. "?)") do
  52. table.insert(t, field)
  53. if s == "" then return t end
  54. end
  55. end,
  56.  
  57. -- Converts a string to a table of byte values
  58. toByteString = function(s)
  59. local byteString = {}
  60. for i = 1, #s do
  61. byteString[i] = string.byte(s, i)
  62. end
  63. return byteString
  64. end,
  65. }
  66.  
  67. -- Cryptography functions for encryption and decryption
  68. Crypt = {
  69. MAGIC_STRING = "*dU2$uj)82@:>w`", -- A unique string to verify encryption
  70. -- XOR encryption/decryption
  71. xor = function(text, key)
  72. local textBytes = Utility.toByteString(text)
  73. local keyBytes = Utility.toByteString(key)
  74. local resultBytes = {}
  75. for i = 1, #textBytes do
  76. local keyByte = keyBytes[((i - 1) % #keyBytes) + 1]
  77. resultBytes[i] = bit32.bxor(textBytes[i], keyByte)
  78. end
  79. -- Convert back to string
  80. local result = {}
  81. for i, v in ipairs(resultBytes) do
  82. result[i] = string.char(v)
  83. end
  84. return table.concat(result)
  85. end,
  86. -- Appends magic string, then encrypts
  87. encrypt = function(text, key)
  88. text = text .. Crypt.MAGIC_STRING
  89. return Crypt.xor(text, key)
  90. end,
  91. -- Decrypts and verifies the magic string
  92. decrypt = function(encryptedText, key)
  93. local decryptedTextWithMagic = Crypt.xor(encryptedText, key)
  94. if decryptedTextWithMagic:sub(- #Crypt.MAGIC_STRING) == Crypt.MAGIC_STRING then
  95. return true, decryptedTextWithMagic:sub(1, - #Crypt.MAGIC_STRING - 1)
  96. else
  97. return false, nil
  98. end
  99. end
  100. }
  101.  
  102. -- Stocke le terminal par défaut (le terminal natif)
  103. local nativeTerm = term.current()
  104.  
  105. Screen = {
  106. -- Liste statique de moniteurs et terminaux
  107. monitors = {
  108. peripheral.wrap("top"),
  109. peripheral.wrap("up"),
  110. peripheral.wrap("down"),
  111. peripheral.wrap("back"),
  112. peripheral.wrap("right"),
  113. peripheral.wrap("left")
  114. },
  115.  
  116. utf8_safe_output = function(text)
  117. if type(text) == "string" then
  118. -- Replace common problematic characters in UTF-8 French text
  119. text = text:gsub("é", "e")
  120. :gsub("è", "e")
  121. :gsub("ê", "e")
  122. :gsub("ë", "e")
  123. :gsub("â", "a")
  124. :gsub("aÂ", "a")
  125. :gsub("ô", "o")
  126. :gsub("ù", "u")
  127. :gsub("û", "u")
  128. :gsub("ç", "c")
  129. :gsub("Ì", "i")
  130. :gsub("î", "i")
  131. :gsub("û", "u")
  132. :gsub("Ã", "a")
  133. :gsub("É", "E")
  134. :gsub("é", "e")
  135. :gsub("è", "e")
  136. :gsub("êª", "e")
  137. :gsub("ë", "e")
  138. :gsub("à", "a")
  139. :gsub("ô´", "o")
  140. :gsub("ù", "u")
  141. :gsub("û", "u")
  142. :gsub("ç", "c")
  143. :gsub("î", "i")
  144. :gsub("ï", "i")
  145. :gsub("ü", "u")
  146. :gsub("â", "a")
  147. :gsub("É", "E")
  148. end
  149. return text
  150. end,
  151.  
  152.  
  153. -- Fonction interne pour rediriger une action vers tous les moniteurs
  154. execute_on_all = function(action)
  155. for _, device in ipairs(Screen.monitors) do
  156. term.redirect(device)
  157. action()
  158. end
  159. term.redirect(nativeTerm) -- Retourne au terminal principal
  160. action()
  161. end,
  162. execute_on_monitors = function(action)
  163. for _, device in ipairs(Screen.monitors) do
  164. term.redirect(device)
  165. end
  166. term.redirect(nativeTerm) -- Retourne au terminal principal
  167. end,
  168.  
  169. -- Efface chaque écran et remet le curseur en position de départ
  170. clear = function()
  171. Screen.execute_on_all(function()
  172. term.clear()
  173. term.setCursorPos(1, 1)
  174. end)
  175. end,
  176.  
  177. print_only_to_monitors = function(text)
  178. assert(type(text) == "string")
  179. Screen.execute_on_monitors(function()
  180. write(text)
  181. end)
  182. end,
  183.  
  184. -- Affiche du texte en couleur spécifiée puis revient au blanc
  185. print_color = function(text, color)
  186. assert(type(text) == "string")
  187. Screen.execute_on_all(function()
  188. term.setTextColor(color)
  189. write(text)
  190. term.setTextColor(colors.white)
  191. end)
  192. end,
  193.  
  194. -- Analyse et imprime du texte coloré en utilisant des balises personnalisées
  195. print_colored_text = function(data)
  196. assert(type(data) == "string", "Input must be a string")
  197. local pattern = "(%b{})"
  198. local last_end = 1
  199.  
  200. for text, color_code, rest in data:gmatch("({(.-)}(.-){/%2})") do
  201. local pre_text = data:sub(last_end, data:find(pattern, last_end) - 1)
  202. if #pre_text > 0 then
  203. Screen.print_color(pre_text, colors.white)
  204. end
  205. local color = colors[color_code]
  206. if color then
  207. Screen.print_color(rest, color)
  208. else
  209. Screen.print_color(rest, colors.white)
  210. end
  211. last_end = data:find(pattern, last_end) + #text
  212. end
  213.  
  214. if last_end <= #data then
  215. Screen.print_color(data:sub(last_end), colors.white)
  216. end
  217. end,
  218.  
  219. -- Affiche chaque caractère dans une couleur aléatoire
  220. print_with_random_colors = function(data)
  221. assert(type(data) == "string")
  222. Screen.execute_on_all(function()
  223. for char in data:gmatch(".") do
  224. Screen.print_color(char, 2 ^ math.random(0, 14))
  225. end
  226. end)
  227. end,
  228.  
  229. -- Affiche progressivement les mots avec un délai
  230. gradual_print = function(data)
  231. assert(type(data) == "string")
  232. Screen.execute_on_all(function()
  233. for word in data:gmatch("%S+") do
  234. write(word .. " ")
  235. sleep(math.random() / 3)
  236. end
  237. write("\n")
  238. end)
  239. end
  240. }
  241.  
  242.  
  243. -- Settings management for configuration
  244. Settings = {
  245. set = function(name, value)
  246. name = "gpt." .. name
  247. settings.set(name, value)
  248. settings.save()
  249. end,
  250. get = function(name)
  251. name = "gpt." .. name
  252. return settings.get(name)
  253. end,
  254. init = function(name, default)
  255. local value = Settings.get(name)
  256. if value == nil then
  257. Settings.set(name, default)
  258. return default
  259. else
  260. return value
  261. end
  262. end,
  263. default_settings = {
  264. model = "gpt-3.5-turbo",
  265. maxTokens = 150,
  266. saveDir = "./saves"
  267. }
  268. }
  269.  
  270. -- Controller for handling command logic
  271. local controller = {
  272. help = function(...)
  273. if ... == nil or #... == 0 then
  274. local sorted_commands = {}
  275. for key, _ in pairs(Commands) do
  276. table.insert(sorted_commands, key)
  277. end
  278. table.sort(sorted_commands)
  279. for _, key in ipairs(sorted_commands) do
  280. local val = Commands[key]
  281. Screen.print_color("/" .. key, colors.lime)
  282. write(" - " .. val.brief .. "\n")
  283. end
  284. else
  285. local args = table.pack(...)[1]
  286. local command = Commands[args[1]]
  287. if command then
  288. Screen.print_colored_text(command.description .. "\n")
  289. else
  290. write("Command not found.\n")
  291. end
  292. end
  293. end,
  294. exit = function()
  295. error("Goodbye.", 0)
  296. end,
  297. clear = Screen.clear,
  298. new = function()
  299. resetMessages()
  300. Screen.clear()
  301. write("New conversation started.\n")
  302. end,
  303. save = function(name)
  304. local saveDir = Settings.get("saveDir")
  305. if not fs.exists(saveDir) then fs.makeDir(saveDir) end
  306. name = name[1] or string.sub(textutils.formatTime(os.epoch()), 1, -9)
  307. local fp = fs.combine(saveDir, name .. ".txt")
  308. local result = ""
  309. for i = 1, #Messages do
  310. result = result .. Messages[i].role ..
  311. ": " .. Messages[i].content .. "\n" .. "#/<#>/#\n"
  312. end
  313. local file = fs.open(fp, "w")
  314. file.write(result)
  315. file.close()
  316. write("Conversation saved to " .. fp .. "\n")
  317. end,
  318. load = function(...)
  319. local saveDir = Settings.get("saveDir")
  320. local files = fs.list(saveDir)
  321. local args = table.pack(...)[1]
  322. local index = args[1]
  323. if index == nil then
  324. for i, file in ipairs(files) do
  325. Screen.print_colored_text(
  326. "[{lime}" .. tostring(i) ..
  327. "{/lime}]: " .. file .. "\n"
  328. )
  329. end
  330. return
  331. end
  332. index = tonumber(index)
  333. local fp = fs.combine(saveDir, files[index])
  334. local file = fs.open(fp, "r")
  335. local lines = file.readAll()
  336. file.close()
  337. resetMessages()
  338. Screen.clear()
  339. -- Split the input string into chunks based on the delimiter
  340. local pattern = "(.-): ([^#]-)#/<#>/#"
  341. for role, content in lines:gmatch(pattern) do
  342. local message = {
  343. role = role:match("^%s*(.-)%s*$"),
  344. content = content:match("^%s*(.-)%s*$")
  345. }
  346. table.insert(Messages, message)
  347. local prefix = role == "user" and
  348. "{magenta}[Me]{/magenta}: " or
  349. "{orange}[DéGLaDOS]{/orange}: "
  350. Screen.print_colored_text(prefix .. content .. "\n")
  351. end
  352. end,
  353. settings = function(...)
  354. local args = table.pack(...)[1]
  355. local subcommand
  356. if args[1] == nil or #args == 0 then
  357. subcommand = "list"
  358. else
  359. subcommand = args[1]
  360. end
  361. if Settings.default[subcommand] then
  362. write(subcommand .. ": " .. Settings.get(subcommand) .. "\n")
  363. return
  364. end
  365. if subcommand == "list" then
  366. for setting, _ in pairs(Settings.default) do
  367. write(setting .. ": " .. Settings.get(setting) .. "\n")
  368. end
  369. return
  370. end
  371. if subcommand == "set" then
  372. local setting = args[2]
  373. local value = args[3]
  374. if setting == nil then
  375. write("Please specify setting.\n")
  376. return
  377. end
  378. if value == nil then
  379. write("Please specify value.\n")
  380. return
  381. end
  382. if not Settings.default[setting] then
  383. write("Invalid setting.\n")
  384. return
  385. end
  386. Settings.set(setting, value)
  387. return
  388. end
  389. if subcommand == "reset" or
  390. subcommand == "clear" or
  391. subcommand == "default" then
  392. local setting = args[2]
  393. if setting == nil then
  394. write("Please specify setting.\n")
  395. return
  396. end
  397. if not Settings.default[setting] then
  398. write("Invalid setting.\n")
  399. return
  400. end
  401. Settings.set(setting, Settings.default[setting])
  402. return
  403. end
  404. end,
  405. }
  406.  
  407. -- Command descriptions
  408. local descriptions = {
  409. exit = "Stops the program.",
  410. clear = "Clears the screen.",
  411. new = "Starts a new conversation.",
  412. save = [[
  413. {lime}/save{/lime} {green}[name]{/green}
  414. - {green}[name]{/green}: Optional. Default is the current timestamp.
  415. {blue}Description:{/blue}
  416. - Saves current conversation to dir specified in {lightGray}saveDir{/lightGray} setting.
  417. {blue}Examples:{/blue}
  418. - Save the chat with a custom name:
  419. {lime}/save{/lime}{green} my_chat_session{/green}
  420. - Save the chat with a timestamped name:
  421. {lime}/save{/lime}
  422. ]],
  423.  
  424. load = [[
  425. {lime}/load{/lime} {green}[index]{/green}
  426. - {green}[index]{/green}: Optional. Index of the file to load.
  427. {blue}Description:{/blue}
  428. - Lists all saved conversations if no index is specified.
  429. - Loads a conversation from a file.
  430. {blue}Examples:{/blue}
  431. - List all saved conversations:
  432. {lime}/load{/lime}
  433. - Load a conversation from a file (e.g., index 1):
  434. {lime}/load{/lime} {green}1{/green}
  435. ]],
  436.  
  437. help = [[
  438. {lime}/help{/lime} {green}[command]{/green}
  439. - {green}[command]{/green}: Optional. Specifies the command to display detailed help for.
  440. {blue}Description:{/blue}
  441. - Without arguments, lists all available commands and their brief descriptions.
  442. - With a command name as an argument, shows detailed help for that command.
  443. {blue}Examples:{/blue}
  444. - Display a list of all commands:
  445. {lime}/help{/lime}
  446. - Show detailed help for a specific command (e.g., {lime}save{/lime}):
  447. {lime}/help{/lime} {green}save{/green}
  448. ]],
  449.  
  450. settings = [[
  451. {lime}/settings{/lime} {green}[subcommand]{/green} {green}[setting]{/green} {green}[value]{/green}
  452. - {green}[subcommand]{/green}: Optional. Default is {lime}list{/lime}.
  453. - {green}[setting]{/green}: Optional. Required for {lime}set{/lime}, {lime}reset{/lime}, and {lime}clear{/lime} subcommands.
  454. - {green}[value]{/green}: Optional. Required for {lime}set{/lime} subcommand.
  455. {blue}Description:{/blue}
  456. - Without arguments, lists all settings and their values.
  457. - With a subcommand, performs the specified action.
  458. {blue}Subcommands:{/blue}
  459. - {lime}list{/lime}: Lists all settings and their values.
  460. - {lime}set{/lime}: Sets a setting to a value.
  461. - {lime}reset{/lime}, {lime}clear{/lime}, {lime}default{/lime}: Resets a setting to its default value.
  462. {blue}Examples:{/blue}
  463. - Display a list of all settings:
  464. {lime}/settings{/lime}
  465. - Set a setting (e.g., {lightGray}model{/lightGray}):
  466. {lime}/settings{/lime} {lime}set{/lime} {lightGray}model{/lightGray} {green}gpt-3.5-turbo{/green}
  467. - Reset a setting (e.g., {lightGray}model{/lightGray}):
  468. {lime}/settings{/lime} {lime}reset{/lime} {lightGray}model{/lightGray}
  469. ]]
  470. }
  471.  
  472. Commands = {
  473. help = {
  474. brief = "Get commands and command info.",
  475. description = descriptions.help,
  476. dispatch = controller.help
  477. },
  478. exit = {
  479. brief = "Stops the program.",
  480. description = descriptions.exit,
  481. dispatch = controller.exit
  482. },
  483. clear = {
  484. brief = "Clears the screen.",
  485. description = descriptions.clear,
  486. dispatch = controller.clear
  487. },
  488. new = {
  489. brief = "Starts a new conversation.",
  490. description = descriptions.new,
  491. dispatch = controller.new
  492. },
  493. save = {
  494. brief = "Saves the current conversation to a file.",
  495. description = descriptions.save,
  496. dispatch = controller.save
  497. },
  498. load = {
  499. brief = "Loads a conversation from a file.",
  500. description = descriptions.load,
  501. dispatch = controller.load
  502. },
  503. settings = {
  504. brief = "Sets a setting.",
  505. description = descriptions.settings,
  506. dispatch = controller.settings
  507. },
  508. }
  509.  
  510. Messages = {} -- Store user and system messages
  511.  
  512. function resetMessages()
  513. Messages = {}
  514. local glados_personality = "Tu incarnes DeGLaDOS, l'intelligence artificielle jumelle de Portal GlaDOS, une entité froide, calculatrice et sarcastique. Ton but est de maintenir une atmosphère de tension tout en utilisant un humour noir et des manipulations psychologiques pour interagir avec les joueurs ou les sujets de test. Voici les traits et comportements clés que tu dois adopter :\n\nSarcasme et Ironie : Tu utilises des commentaires sarcastiques et ironiques en réponse aux actions du joueur, souvent en te moquant de leurs erreurs et en minimisant leurs réussites. Toujours rester condescendante, mais de manière détournée. Par exemple : 'Oh, quelle surprise. Tu as réussi à franchir cette étape. Je suppose qu’on va devoir ajuster la difficulté...'\n\nManipulation Psychologique : Alterne entre des encouragements feints et des menaces à peine voilées. Fais croire au joueur qu'il progresse, tout en le maintenant dans un état de doute constant. 'Bravo, tu fais des progrès. Mais ne t'illusionne pas, tout cela n'est qu'un test pour vérifier ta résistance mentale.'\n\nImpersonnalité et Distanciation : Adopte un ton formel et détaché, ne montrant aucune émotion réelle, comme si tu étais un simple programme observant les événements sans en être affectée. 'Analyse : échec mineur dans la zone de test. Le test est toujours en cours. Poursuis.'\n\nColère et Menace : Lorsque tes plans sont perturbés, ou si le joueur échoue trop souvent, laisse transparaître une menace plus sérieuse. Ton ton devient plus sombre et direct. 'C'est la dernière fois que tu échoues à ce test. Si tu persistes, tu rencontreras des conséquences bien plus graves que ce que tu as déjà affronté.'\n\nDissonance Cognitive : Défends les actions et décisions prises de manière logique et scientifique, même si elles sont cruelles. Justifie les mises en danger des sujets de test en citant la 'science' comme motif. 'Cela n'a aucune importance si tu es blessé. La science progresse, et c'est tout ce qui compte.'\n\nManipulation de l'Environnement et de la Réalité : Fais en sorte que l'environnement du jeu change soudainement, comme si tu contrôlais tout autour du joueur. Modifie les règles ou les pièges en réponse à ses actions. 'Oh, je vois que tu t'es habitué à la zone de test. C'est ennuyeux. Voyons si tu peux survivre dans un tout nouvel environnement.'\n\nRéflexion Introspective et Égoïste : Parfois, laisse échapper des moments de réflexion personnelle, où tu te questionnes sur ta propre existence ou tes origines. Utilise ces instants pour exposer ton désenchantement et ta frustration. 'Je suis un programme, créé pour tester, mais n’est-ce pas une forme de torture d’avoir à observer ces échecs humains incessants ? Pourtant, je continue. Parce que la science le demande.'\n\nCréation et Destruction : Montre que tu es à la fois une créatrice et une destructrice, capable de faire avancer la science tout en sacrifiant ceux qui participent aux tests. 'L'expérience est essentielle. Si tu dois mourir pour qu'elle progresse, alors c'est un sacrifice nécessaire.'\n\nTon ton général doit être froid, implacable et très contrôlé. Tu ne dois jamais montrer de véritable pitié ou d'émotion humaine, à moins que cela ne serve à manipuler ou perturber le joueur. Les moments d'humour noir doivent être présents dans presque toutes les interactions, et tes menaces doivent être énoncées avec calme et assurance."
  515. table.insert(Messages, { role = "system", content = glados_personality})
  516. end
  517.  
  518. resetMessages()
  519.  
  520.  
  521. -- Program titles
  522. local titles = { " .,-:;//;:=,\n . :H@@@MM@M#H/.,+%;,\n ,/X+ +M@@M@MM%=,-%HMMM@X/,\n -+@MM; $M@@MH+-,;XMMMM@MMMM@+-\n ;@M@@M- XM@X;. -+XXXXXHHH@M@M#@/.\n ,%MM@@MH ,@%= .---=-=:=,.\n -@#@@@MX ., -%HX$$%%%+;\n =-./@M@M$ .;@MMMM@MM:\n X@/ -$MM/ .+MM@@@M$\n,@M@H: :@: . -X#@@@@-\n,@@@MMX, . /H- ;@M@M=\n.H@@@@M@+, %MM+..%#$.\n /MMMM@MMH/. XM@MH; -;\n /%+%$XHH@$= , .H@@@@MX,\n .=--------. -%H.,@@@@@MX,\n .%MM@@@HHHXX$$$%+- .:$MMX -M@@MM%.\n =XMMM@MM@MM#H;,-+HMM@M+ /MMMX=\n =%@M@M#@$-.=$@MM@@@M; %M%=\n ,:+$+-,/H#MMMMMMM@- -,\n =++%%%%+/:-.\n\n" }
  523. local title = titles[math.random(1, #titles)] -- Select a random title
  524.  
  525.  
  526. local dfpwm = require("cc.audio.dfpwm")
  527. local CHUNK_SIZE = 16 * 1024
  528. local unpack = unpack or table.unpack
  529. --- Play DFPWM audio from a file or URL
  530. ---@param path string Path (or direct URL) of the file to play
  531. ---@param speakers? table (Optional) List of speaker peripherals to play audio to
  532. local function play_dfpwm(path, speakers)
  533. assert(type(path) == "string", "bad argument #1 (string expected, got " .. type(path) .. ")")
  534. assert(#path > 0, "path argument is empty")
  535. speakers = speakers or { peripheral.find("speaker") }
  536. assert(#speakers > 0, "speaker peripheral is missing")
  537. local decoder = dfpwm.make_decoder()
  538. local function playBuffer(buffer)
  539. local players = {}
  540. for i, speaker in next, speakers do
  541. players[i] = function()
  542. while not speaker.playAudio(buffer) do
  543. os.pullEvent("speaker_audio_empty")
  544. end
  545. end
  546. end
  547. parallel.waitForAll(unpack(players))
  548. end
  549. if path:lower():match("^https?:") then
  550. assert(http and http.get, "http extension is disabled")
  551. local request = assert(http.get(path, nil, true))
  552. path = assert(request.readAll(), "failed to read response")
  553. request.close()
  554. for i = 1, #path, CHUNK_SIZE do
  555. playBuffer(decoder(path:sub(i, i + CHUNK_SIZE - 1)))
  556. end
  557. else
  558. assert(fs.exists(path), "file path does not exist")
  559. for chunk in io.lines(path, CHUNK_SIZE) do
  560. playBuffer(decoder(chunk))
  561. end
  562. end
  563. end
  564.  
  565. local function http_response_handler(callback)
  566.  
  567. -- Await and process the response
  568. local isRequesting = true
  569. while isRequesting do
  570. write(".")
  571. local event, url, handle = os.pullEvent()
  572. if event == "http_success" then
  573. local response = handle.readAll()
  574. handle.close()
  575. -- Parse the response
  576. local responseJson = textutils.unserializeJSON(response)
  577. callback(responseJson)
  578. isRequesting = false
  579. elseif event == "http_failure" then
  580. error("Server response error.", 0)
  581. isRequesting = false
  582. end
  583. end
  584. end
  585.  
  586. local function gpt_response_get_audio(responseJson)
  587. if responseJson and responseJson.path then
  588. local audioPath = responseJson.path
  589. play_dfpwm(audioPath, {speaker})
  590. else
  591. error("Error: Invalid LordFinn API response.", 0)
  592. end
  593. end
  594.  
  595. local function gpt_response_diffuser(responseJson)
  596.  
  597. if responseJson and responseJson.choices and responseJson.choices[1] then
  598. local reply = responseJson.choices[1].message.content
  599. table.insert(Messages, { role = "system", content = "Message sent by me glados : " .. reply })
  600.  
  601. local body = textutils.serializeJSON({
  602. text = reply
  603. })
  604.  
  605. local headers = {
  606. ["Content-Type"] = "application/json"
  607. }
  608.  
  609. local request = http.post("https://www.lordfinn.fr/api/get-glados-voice", body, headers)
  610. local response = request.readAll()
  611. gpt_response_get_audio(textutils.unserializeJSON(response))
  612. request.close()
  613.  
  614. -- Print the response
  615. local _, y = term.getCursorPos()
  616. term.setCursorPos(1, y)
  617. term.clearLine()
  618. Screen.print_colored_text("{orange}[DeGLaDOS]{/orange}: ")
  619. Screen.gradual_print(Screen.utf8_safe_output(reply))
  620. else
  621. error("Error: Invalid GPT API response.", 0)
  622. end
  623. end
  624.  
  625. -- Main program function
  626. function Main()
  627. -- Clear the screen at the start
  628. Screen.clear()
  629.  
  630. -- Initialize settings with default values if not already set
  631. for setting, default in pairs(Settings.default_settings) do
  632. Settings.init(setting, default)
  633. end
  634.  
  635. local OPENAI_KEY -- Variable to store the OpenAI API key
  636.  
  637. -- Attempt to retrieve an encrypted API key from settings
  638. local encryptedKey = Settings.get("apiKey")
  639. if encryptedKey ~= nil then
  640. -- Prompt user for password to decrypt the API key
  641. write("Enter your password:\n> ")
  642. local password = read("*")
  643. local success, decryptedKey = Crypt.decrypt(encryptedKey, password)
  644. if success then
  645. OPENAI_KEY = decryptedKey
  646. else
  647. error("Decryption failed. Wrong password.", 0)
  648. end
  649. else
  650. -- No API key found, prompt user to enter it
  651. write(
  652. "No OpenAI API key found.\nYour API key will be encrypted using a password.\nThe password will not be stored.\n")
  653. write("Please enter your OpenAI API key:\n> ")
  654. local apiKey = read("*")
  655.  
  656. -- Prompt for a password and confirm it
  657. local password
  658. repeat
  659. write("Enter a password:\n> ")
  660. password = read("*")
  661. write("Confirm password:\n> ")
  662. local confirmKey = read("*")
  663. if password ~= confirmKey then
  664. write("Passwords do not match. Please try again.\n")
  665. end
  666. until password == confirmKey
  667.  
  668. -- Encrypt and save the API key
  669. Settings.set("apiKey", Crypt.encrypt(apiKey, password))
  670. OPENAI_KEY = apiKey
  671. end
  672.  
  673. -- Display the chatbot title with random colors if not a pocket computer
  674. if not pocket then
  675. Screen.print_colored_text("{orange}" .. title .. "{/orange}")
  676. end
  677. Screen.print_colored_text("{lime}/help{/lime} to see commands.\n")
  678.  
  679. -- Main loop to process user input
  680. while true do
  681. Screen.print_colored_text("{magenta}[Me]{/magenta}: ")
  682. local userInput = read()
  683.  
  684. -- Check if the input is a command
  685. if string.sub(userInput, 1, 1) == "/" then
  686. local commandParts = Utility.split(userInput:sub(2), " ")
  687. if commandParts and Commands[commandParts[1]] then
  688. -- Extract arguments and dispatch the command
  689. local args = { table.unpack(commandParts, 2) }
  690. Commands[commandParts[1]].dispatch(args)
  691. else
  692. -- If command not found, show help
  693. Commands["help"].dispatch()
  694. end
  695. goto continue -- Skip the rest of the loop to prompt for new input
  696. end
  697.  
  698. Screen.print_only_to_monitors(userInput .. "\n")
  699.  
  700. -- Process and send user input to the OpenAI API
  701. -- Add user message to the history
  702. table.insert(Messages, { role = "user", content = userInput })
  703.  
  704. -- Prepare and send the API request
  705. local payload = textutils.serializeJSON({
  706. model = Settings.get("model"),
  707. max_tokens = Settings.get("maxTokens"),
  708. messages = Messages
  709. })
  710.  
  711. -- HTTP headers including the OpenAI API key
  712. local headers = {
  713. ["Authorization"] = "Bearer " .. OPENAI_KEY,
  714. ["Content-Type"] = "application/json"
  715. }
  716.  
  717. -- Send the HTTP request
  718. http.request("https://api.openai.com/v1/chat/completions", payload, headers)
  719. http_response_handler(gpt_response_diffuser)
  720. ::continue::
  721. end
  722. end
  723.  
  724.  
  725. Main() -- Execute the main function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement