feat(ai): add trapgpt chat bot
This commit is contained in:
parent
8e9958b1c3
commit
ccf6e685ce
@ -6,6 +6,7 @@ local DEFAULT_POLL_TIMEOUT_SECONDS = 300;
|
|||||||
local DEFAULT_POLL_INTERVAL_SECONDS = 2;
|
local DEFAULT_POLL_INTERVAL_SECONDS = 2;
|
||||||
local DEFAULT_LUA_EXEC_MAX_RETRIES = 2;
|
local DEFAULT_LUA_EXEC_MAX_RETRIES = 2;
|
||||||
local DEFAULT_LUA_EXEC_TIMEOUT_SECONDS = 5;
|
local DEFAULT_LUA_EXEC_TIMEOUT_SECONDS = 5;
|
||||||
|
local DEFAULT_SESSION_SETTING_KEY = 'opencc.session_id';
|
||||||
|
|
||||||
local B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
local B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
|
||||||
@ -255,9 +256,9 @@ local function createAi(opts)
|
|||||||
return nil;
|
return nil;
|
||||||
end
|
end
|
||||||
|
|
||||||
local function handleMissingSession(persist)
|
local function handleMissingSession(persist, sessionSettingKey)
|
||||||
if persist then
|
if persist then
|
||||||
settingsLib.unset('opencc.session_id');
|
settingsLib.unset(sessionSettingKey or DEFAULT_SESSION_SETTING_KEY);
|
||||||
if settingsLib.save then settingsLib.save(); end
|
if settingsLib.save then settingsLib.save(); end
|
||||||
end
|
end
|
||||||
return false, 'session introuvable; lance: ai new <prompt>';
|
return false, 'session introuvable; lance: ai new <prompt>';
|
||||||
@ -266,7 +267,7 @@ local function createAi(opts)
|
|||||||
local doGet;
|
local doGet;
|
||||||
local doPost;
|
local doPost;
|
||||||
|
|
||||||
local function pollMessage(cfg, sessionId, messageId, persist)
|
local function pollMessage(cfg, sessionId, messageId, persist, sessionSettingKey)
|
||||||
local loop = eventloopFactory();
|
local loop = eventloopFactory();
|
||||||
local deadline = nowFunc() + cfg.pollTimeoutSeconds;
|
local deadline = nowFunc() + cfg.pollTimeoutSeconds;
|
||||||
local resultOk, resultValue;
|
local resultOk, resultValue;
|
||||||
@ -280,7 +281,7 @@ local function createAi(opts)
|
|||||||
local body, code = doGet(cfg, '/session/' .. sessionId .. '/message');
|
local body, code = doGet(cfg, '/session/' .. sessionId .. '/message');
|
||||||
if not body then return finish(false, code); end
|
if not body then return finish(false, code); end
|
||||||
if code == 404 then
|
if code == 404 then
|
||||||
local ok, value = handleMissingSession(persist);
|
local ok, value = handleMissingSession(persist, sessionSettingKey);
|
||||||
return finish(ok, value);
|
return finish(ok, value);
|
||||||
end
|
end
|
||||||
if code and code ~= 200 then
|
if code and code ~= 200 then
|
||||||
@ -349,8 +350,9 @@ local function createAi(opts)
|
|||||||
});
|
});
|
||||||
end
|
end
|
||||||
|
|
||||||
function api.clearSession()
|
function api.clearSession(options)
|
||||||
settingsLib.unset('opencc.session_id');
|
options = options or {};
|
||||||
|
settingsLib.unset(options.sessionSettingKey or DEFAULT_SESSION_SETTING_KEY);
|
||||||
if settingsLib.save then settingsLib.save(); end
|
if settingsLib.save then settingsLib.save(); end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -385,9 +387,10 @@ local function createAi(opts)
|
|||||||
if not cfg then return false, err; end
|
if not cfg then return false, err; end
|
||||||
|
|
||||||
local persist = options.persist ~= false;
|
local persist = options.persist ~= false;
|
||||||
|
local sessionSettingKey = options.sessionSettingKey or DEFAULT_SESSION_SETTING_KEY;
|
||||||
local sessionId = options.sessionId;
|
local sessionId = options.sessionId;
|
||||||
if persist and sessionId == nil then
|
if persist and sessionId == nil then
|
||||||
sessionId = settingsLib.get('opencc.session_id');
|
sessionId = settingsLib.get(sessionSettingKey);
|
||||||
end
|
end
|
||||||
|
|
||||||
if not sessionId or sessionId == '' then
|
if not sessionId or sessionId == '' then
|
||||||
@ -402,7 +405,7 @@ local function createAi(opts)
|
|||||||
end
|
end
|
||||||
sessionId = decoded.id;
|
sessionId = decoded.id;
|
||||||
if persist then
|
if persist then
|
||||||
settingsLib.set('opencc.session_id', sessionId);
|
settingsLib.set(sessionSettingKey, sessionId);
|
||||||
if settingsLib.save then settingsLib.save(); end
|
if settingsLib.save then settingsLib.save(); end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -414,7 +417,7 @@ local function createAi(opts)
|
|||||||
});
|
});
|
||||||
if not body then return false, code; end
|
if not body then return false, code; end
|
||||||
if code == 404 then
|
if code == 404 then
|
||||||
return handleMissingSession(persist);
|
return handleMissingSession(persist, sessionSettingKey);
|
||||||
end
|
end
|
||||||
if code and code ~= 204 and code ~= 200 then
|
if code and code ~= 204 and code ~= 200 then
|
||||||
return false, 'erreur message: HTTP ' .. tostring(code);
|
return false, 'erreur message: HTTP ' .. tostring(code);
|
||||||
@ -428,7 +431,7 @@ local function createAi(opts)
|
|||||||
return true, { reply = reply, sessionId = sessionId, messageId = messageId };
|
return true, { reply = reply, sessionId = sessionId, messageId = messageId };
|
||||||
end
|
end
|
||||||
|
|
||||||
return pollMessage(cfg, sessionId, messageId, persist);
|
return pollMessage(cfg, sessionId, messageId, persist, sessionSettingKey);
|
||||||
end
|
end
|
||||||
|
|
||||||
function api.createLuaExecutor(options)
|
function api.createLuaExecutor(options)
|
||||||
|
|||||||
178
apis/libtrapgpt.lua
Normal file
178
apis/libtrapgpt.lua
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
local DEFAULT_THROTTLE_SECONDS = 5;
|
||||||
|
local DEFAULT_MAX_REPLY_CHARS = 160;
|
||||||
|
local DEFAULT_PREFIX = 'TrapGPT';
|
||||||
|
local DEFAULT_SESSION_SETTING_KEY = 'trapgpt.opencc.session_id';
|
||||||
|
local SILENCE = 'SILENCE';
|
||||||
|
|
||||||
|
local function nowSeconds()
|
||||||
|
if os.epoch then
|
||||||
|
return os.epoch('utc') / 1000;
|
||||||
|
end
|
||||||
|
return os.clock();
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resolveNumber(value, defaultValue)
|
||||||
|
local n = tonumber(value);
|
||||||
|
if not n or n < 0 then return defaultValue; end
|
||||||
|
return n;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trim(s)
|
||||||
|
return tostring(s or ''):gsub('^%s+', ''):gsub('%s+$', '');
|
||||||
|
end
|
||||||
|
|
||||||
|
local function truncate(s, maxChars)
|
||||||
|
s = trim(s);
|
||||||
|
if #s <= maxChars then return s; end
|
||||||
|
if maxChars <= 3 then return string.sub(s, 1, maxChars); end
|
||||||
|
return string.sub(s, 1, maxChars - 3) .. '...';
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatChatLine(message)
|
||||||
|
local at = message.at and ('@' .. tostring(math.floor(message.at)) .. ' ') or '';
|
||||||
|
return at .. tostring(message.username or '?') .. ': ' .. tostring(message.text or '');
|
||||||
|
end
|
||||||
|
|
||||||
|
local function buildPrompt(messages, firstBatch, maxReplyChars)
|
||||||
|
local lines = {
|
||||||
|
'Tu es TrapGPT dans le chat Minecraft.',
|
||||||
|
'Reponds seulement si utile.',
|
||||||
|
'Reponse tres concise: une phrase courte, maximum ' .. tostring(maxReplyChars) .. ' caracteres.',
|
||||||
|
'Pas de markdown. Ne repete pas l historique.',
|
||||||
|
'Si aucune reponse utile, reponds exactement: ' .. SILENCE,
|
||||||
|
};
|
||||||
|
if firstBatch then
|
||||||
|
lines[#lines + 1] = 'Contexte initial: voici les premiers messages recus.';
|
||||||
|
end
|
||||||
|
lines[#lines + 1] = '';
|
||||||
|
lines[#lines + 1] = 'Nouveaux messages chat depuis le dernier envoi:';
|
||||||
|
for _, message in ipairs(messages) do
|
||||||
|
lines[#lines + 1] = formatChatLine(message);
|
||||||
|
end
|
||||||
|
return table.concat(lines, '\n');
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createTrapGpt(opts)
|
||||||
|
opts = opts or {};
|
||||||
|
|
||||||
|
local settingsLib = opts.settings or settings;
|
||||||
|
local nowFunc = opts.now or nowSeconds;
|
||||||
|
local sleepFunc = opts.sleep or sleep;
|
||||||
|
local ai = opts.ai or require('/apis/libai')();
|
||||||
|
local chatBox = opts.chatBox;
|
||||||
|
local log = opts.log or print;
|
||||||
|
|
||||||
|
local api = {};
|
||||||
|
local history = {};
|
||||||
|
local sentIndex = 0;
|
||||||
|
local firstBatch = true;
|
||||||
|
local lastSendAt = 0;
|
||||||
|
local active = false;
|
||||||
|
local stopped = false;
|
||||||
|
|
||||||
|
local function throttleSeconds()
|
||||||
|
return resolveNumber(settingsLib.get('trapgpt.throttle_seconds'), DEFAULT_THROTTLE_SECONDS);
|
||||||
|
end
|
||||||
|
|
||||||
|
local function maxReplyChars()
|
||||||
|
return math.max(1, resolveNumber(settingsLib.get('trapgpt.max_reply_chars'), DEFAULT_MAX_REPLY_CHARS));
|
||||||
|
end
|
||||||
|
|
||||||
|
local function prefix()
|
||||||
|
local value = settingsLib.get('trapgpt.prefix');
|
||||||
|
if type(value) ~= 'string' or value == '' then return DEFAULT_PREFIX; end
|
||||||
|
return value;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function queuedMessages()
|
||||||
|
local messages = {};
|
||||||
|
for i = sentIndex + 1, #history do
|
||||||
|
messages[#messages + 1] = history[i];
|
||||||
|
end
|
||||||
|
return messages;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function shouldIgnore(username, text, isHidden)
|
||||||
|
if isHidden then return true; end
|
||||||
|
if type(text) ~= 'string' or trim(text) == '' then return true; end
|
||||||
|
if type(username) == 'string' and username == prefix() then return true; end
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.onChat(username, message, uuid, isHidden, messageUtf8)
|
||||||
|
local text = messageUtf8 or message;
|
||||||
|
if shouldIgnore(username, text, isHidden) then return false; end
|
||||||
|
history[#history + 1] = {
|
||||||
|
username = username,
|
||||||
|
text = text,
|
||||||
|
uuid = uuid,
|
||||||
|
at = nowFunc(),
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.pendingCount()
|
||||||
|
return #history - sentIndex;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.history()
|
||||||
|
return history;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.buildPrompt(messages)
|
||||||
|
return buildPrompt(messages, firstBatch, maxReplyChars());
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.processOnce()
|
||||||
|
if active or api.pendingCount() <= 0 then return false; end
|
||||||
|
|
||||||
|
local waitSeconds = throttleSeconds() - (nowFunc() - lastSendAt);
|
||||||
|
if waitSeconds > 0 then sleepFunc(waitSeconds); end
|
||||||
|
|
||||||
|
local startIndex = sentIndex + 1;
|
||||||
|
local messages = queuedMessages();
|
||||||
|
if #messages == 0 then return false; end
|
||||||
|
|
||||||
|
active = true;
|
||||||
|
local ok, result = ai.ask(buildPrompt(messages, firstBatch, maxReplyChars()), {
|
||||||
|
sessionSettingKey = DEFAULT_SESSION_SETTING_KEY,
|
||||||
|
sessionTitle = 'trapgpt',
|
||||||
|
});
|
||||||
|
active = false;
|
||||||
|
lastSendAt = nowFunc();
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
log('trapgpt ai error: ' .. tostring(result));
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
sentIndex = startIndex + #messages - 1;
|
||||||
|
firstBatch = false;
|
||||||
|
|
||||||
|
local reply = truncate(result.reply, maxReplyChars());
|
||||||
|
if reply == '' or reply == SILENCE then return true; end
|
||||||
|
if chatBox then
|
||||||
|
local sent, err = chatBox.sendMessage(reply, prefix());
|
||||||
|
if not sent then log('trapgpt chat error: ' .. tostring(err)); end
|
||||||
|
end
|
||||||
|
return true, reply;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.stop()
|
||||||
|
stopped = true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function api.run()
|
||||||
|
while not stopped do
|
||||||
|
if api.pendingCount() > 0 then
|
||||||
|
api.processOnce();
|
||||||
|
else
|
||||||
|
sleepFunc(0.25);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return api;
|
||||||
|
end
|
||||||
|
|
||||||
|
return createTrapGpt;
|
||||||
@ -5,7 +5,9 @@
|
|||||||
"dependencies": ["trapos-core"],
|
"dependencies": ["trapos-core"],
|
||||||
"files": [
|
"files": [
|
||||||
"apis/libai.lua",
|
"apis/libai.lua",
|
||||||
"programs/ai.lua"
|
"apis/libtrapgpt.lua",
|
||||||
|
"programs/ai.lua",
|
||||||
|
"programs/trapgpt.lua"
|
||||||
],
|
],
|
||||||
"autostart": []
|
"autostart": []
|
||||||
}
|
}
|
||||||
|
|||||||
54
programs/trapgpt.lua
Normal file
54
programs/trapgpt.lua
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
local createTrapGpt = require('/apis/libtrapgpt');
|
||||||
|
local createVersion = require('/apis/libversion');
|
||||||
|
|
||||||
|
local args = table.pack(...);
|
||||||
|
|
||||||
|
local function printUsage()
|
||||||
|
print('trapgpt usage:');
|
||||||
|
print();
|
||||||
|
print(' trapgpt');
|
||||||
|
print(' trapgpt --version');
|
||||||
|
print(' trapgpt --help');
|
||||||
|
print();
|
||||||
|
print('settings required:');
|
||||||
|
print(' opencc.server_url');
|
||||||
|
print();
|
||||||
|
print('settings optional:');
|
||||||
|
print(' trapgpt.throttle_seconds (default: 5)');
|
||||||
|
print(' trapgpt.max_reply_chars (default: 160)');
|
||||||
|
print(' trapgpt.prefix (default: TrapGPT)');
|
||||||
|
end
|
||||||
|
|
||||||
|
local command = args[1];
|
||||||
|
|
||||||
|
if command == '--version' or command == '-version' or command == 'version' then
|
||||||
|
print('v' .. createVersion().forSelf());
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if command == '--help' or command == '-help' or command == 'help' then
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.n > 0 then
|
||||||
|
printUsage();
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local chatBox = peripheral.find('chat_box') or peripheral.find('chatBox');
|
||||||
|
if not chatBox then
|
||||||
|
error('chat_box peripheral not found');
|
||||||
|
end
|
||||||
|
|
||||||
|
local trapgpt = createTrapGpt({ chatBox = chatBox });
|
||||||
|
|
||||||
|
local function listenChat()
|
||||||
|
while true do
|
||||||
|
local _, username, message, uuid, isHidden, messageUtf8 = os.pullEvent('chat');
|
||||||
|
trapgpt.onChat(username, message, uuid, isHidden, messageUtf8);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print('trapgpt listening');
|
||||||
|
parallel.waitForAny(listenChat, function() trapgpt.run(); end);
|
||||||
48
tests/ai.lua
48
tests/ai.lua
@ -275,6 +275,21 @@ testlib.test('ask saves new session_id to settings', function()
|
|||||||
testlib.assertEquals(settingsStub.saveCount(), 1);
|
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
testlib.test('ask saves custom sessionSettingKey to settings', function()
|
||||||
|
local httpStub = fakeHttp(
|
||||||
|
{ sessionResp('ses_trapgpt'), messageResp('reply') },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
local settingsStub = fakeSettings({ ['opencc.server_url'] = 'http://host' });
|
||||||
|
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||||
|
|
||||||
|
ai.ask('hello', { sessionSettingKey = 'trapgpt.opencc.session_id' });
|
||||||
|
|
||||||
|
testlib.assertEquals(settingsStub.values['trapgpt.opencc.session_id'], 'ses_trapgpt');
|
||||||
|
testlib.assertEquals(settingsStub.values['opencc.session_id'], nil);
|
||||||
|
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||||
|
end);
|
||||||
|
|
||||||
testlib.test('ask reuses existing session_id without creating a new session', function()
|
testlib.test('ask reuses existing session_id without creating a new session', function()
|
||||||
local httpStub = fakeHttp(
|
local httpStub = fakeHttp(
|
||||||
{ messageResp('reply') },
|
{ messageResp('reply') },
|
||||||
@ -293,6 +308,25 @@ testlib.test('ask reuses existing session_id without creating a new session', fu
|
|||||||
testlib.assertTrue(string.find(httpStub.postCalls[1].url, '/session/ses_existing/prompt_async', 1, true) ~= nil);
|
testlib.assertTrue(string.find(httpStub.postCalls[1].url, '/session/ses_existing/prompt_async', 1, true) ~= nil);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
testlib.test('ask reuses custom sessionSettingKey without creating a new session', function()
|
||||||
|
local httpStub = fakeHttp(
|
||||||
|
{ messageResp('reply') },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
local settingsStub = fakeSettings({
|
||||||
|
['opencc.server_url'] = 'http://host',
|
||||||
|
['opencc.session_id'] = 'ses_other',
|
||||||
|
['trapgpt.opencc.session_id'] = 'ses_trapgpt',
|
||||||
|
});
|
||||||
|
local ai = createAi({ http = httpStub, settings = settingsStub });
|
||||||
|
|
||||||
|
local ok = ai.ask('hello', { sessionSettingKey = 'trapgpt.opencc.session_id' });
|
||||||
|
|
||||||
|
testlib.assertTrue(ok);
|
||||||
|
testlib.assertEquals(#httpStub.postCalls, 1);
|
||||||
|
testlib.assertTrue(string.find(httpStub.postCalls[1].url, '/session/ses_trapgpt/prompt_async', 1, true) ~= nil);
|
||||||
|
end);
|
||||||
|
|
||||||
testlib.test('ask sends exact prompt text', function()
|
testlib.test('ask sends exact prompt text', function()
|
||||||
local httpStub = fakeHttp(
|
local httpStub = fakeHttp(
|
||||||
{ messageResp('reply') },
|
{ messageResp('reply') },
|
||||||
@ -940,4 +974,18 @@ testlib.test('clearSession unsets persisted session id', function()
|
|||||||
testlib.assertEquals(settingsStub.saveCount(), 1);
|
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||||
end);
|
end);
|
||||||
|
|
||||||
|
testlib.test('clearSession unsets custom sessionSettingKey', function()
|
||||||
|
local settingsStub = fakeSettings({
|
||||||
|
['opencc.session_id'] = 'ses_old',
|
||||||
|
['trapgpt.opencc.session_id'] = 'ses_trapgpt',
|
||||||
|
});
|
||||||
|
local ai = createAi({ http = fakeHttp({}, {}), settings = settingsStub });
|
||||||
|
|
||||||
|
ai.clearSession({ sessionSettingKey = 'trapgpt.opencc.session_id' });
|
||||||
|
|
||||||
|
testlib.assertEquals(settingsStub.values['opencc.session_id'], 'ses_old');
|
||||||
|
testlib.assertEquals(settingsStub.values['trapgpt.opencc.session_id'], nil);
|
||||||
|
testlib.assertEquals(settingsStub.saveCount(), 1);
|
||||||
|
end);
|
||||||
|
|
||||||
testlib.run();
|
testlib.run();
|
||||||
|
|||||||
155
tests/trapgpt.lua
Normal file
155
tests/trapgpt.lua
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
local createLibTest = require('/apis/libtest');
|
||||||
|
local createTrapGpt = require('/apis/libtrapgpt');
|
||||||
|
|
||||||
|
local testlib = createLibTest({ ... });
|
||||||
|
|
||||||
|
local function fakeSettings(values)
|
||||||
|
values = values or {};
|
||||||
|
return {
|
||||||
|
get = function(key) return values[key]; end,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fakeAi(replies)
|
||||||
|
local calls = {};
|
||||||
|
local idx = 0;
|
||||||
|
return {
|
||||||
|
ask = function(prompt, options)
|
||||||
|
idx = idx + 1;
|
||||||
|
calls[#calls + 1] = { prompt = prompt, options = options };
|
||||||
|
local reply = replies[idx];
|
||||||
|
if reply == false then return false, 'ai failed'; end
|
||||||
|
return true, { reply = reply or 'ok' };
|
||||||
|
end,
|
||||||
|
calls = calls,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fakeChatBox()
|
||||||
|
local messages = {};
|
||||||
|
return {
|
||||||
|
sendMessage = function(message, prefix)
|
||||||
|
messages[#messages + 1] = { message = message, prefix = prefix };
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
messages = messages,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
testlib.test('trapgpt queues visible chat messages only', function()
|
||||||
|
local ai = fakeAi({ 'hi' });
|
||||||
|
local chatBox = fakeChatBox();
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = chatBox,
|
||||||
|
settings = fakeSettings({ trapgpt_throttle_seconds = 0 }),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 10; end,
|
||||||
|
});
|
||||||
|
|
||||||
|
testlib.assertEquals(bot.onChat('alice', 'hello', 'uuid-a', false), true);
|
||||||
|
testlib.assertEquals(bot.onChat('bob', '$secret', 'uuid-b', true), false);
|
||||||
|
testlib.assertEquals(bot.pendingCount(), 1);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('trapgpt sends queued messages in one prompt', function()
|
||||||
|
local ai = fakeAi({ 'bonjour' });
|
||||||
|
local chatBox = fakeChatBox();
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = chatBox,
|
||||||
|
settings = fakeSettings({ ['trapgpt.throttle_seconds'] = 0 }),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 20; end,
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.onChat('alice', 'one', nil, false);
|
||||||
|
bot.onChat('bob', 'two', nil, false);
|
||||||
|
local processed, reply = bot.processOnce();
|
||||||
|
|
||||||
|
testlib.assertEquals(processed, true);
|
||||||
|
testlib.assertEquals(reply, 'bonjour');
|
||||||
|
testlib.assertEquals(#ai.calls, 1);
|
||||||
|
testlib.assertTrue(ai.calls[1].prompt:find('alice: one', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(ai.calls[1].prompt:find('bob: two', 1, true) ~= nil);
|
||||||
|
testlib.assertEquals(ai.calls[1].options.sessionSettingKey, 'trapgpt.opencc.session_id');
|
||||||
|
testlib.assertEquals(#chatBox.messages, 1);
|
||||||
|
testlib.assertEquals(chatBox.messages[1].prefix, 'TrapGPT');
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('trapgpt only sends missing messages after success', function()
|
||||||
|
local ai = fakeAi({ 'first', 'second' });
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = fakeChatBox(),
|
||||||
|
settings = fakeSettings({ ['trapgpt.throttle_seconds'] = 0 }),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 30; end,
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.onChat('alice', 'one', nil, false);
|
||||||
|
bot.processOnce();
|
||||||
|
bot.onChat('bob', 'two', nil, false);
|
||||||
|
bot.processOnce();
|
||||||
|
|
||||||
|
testlib.assertEquals(#ai.calls, 2);
|
||||||
|
testlib.assertTrue(ai.calls[2].prompt:find('bob: two', 1, true) ~= nil);
|
||||||
|
testlib.assertTrue(ai.calls[2].prompt:find('alice: one', 1, true) == nil);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('trapgpt keeps queue when ai fails', function()
|
||||||
|
local ai = fakeAi({ false, 'retry' });
|
||||||
|
local chatBox = fakeChatBox();
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = chatBox,
|
||||||
|
settings = fakeSettings({ ['trapgpt.throttle_seconds'] = 0 }),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 40; end,
|
||||||
|
log = function() end,
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.onChat('alice', 'one', nil, false);
|
||||||
|
testlib.assertEquals(bot.processOnce(), false);
|
||||||
|
testlib.assertEquals(bot.pendingCount(), 1);
|
||||||
|
testlib.assertEquals(bot.processOnce(), true);
|
||||||
|
testlib.assertEquals(bot.pendingCount(), 0);
|
||||||
|
testlib.assertEquals(#chatBox.messages, 1);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('trapgpt does not send SILENCE replies', function()
|
||||||
|
local ai = fakeAi({ 'SILENCE' });
|
||||||
|
local chatBox = fakeChatBox();
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = chatBox,
|
||||||
|
settings = fakeSettings({ ['trapgpt.throttle_seconds'] = 0 }),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 50; end,
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.onChat('alice', 'one', nil, false);
|
||||||
|
testlib.assertEquals(bot.processOnce(), true);
|
||||||
|
testlib.assertEquals(#chatBox.messages, 0);
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.test('trapgpt truncates long replies', function()
|
||||||
|
local ai = fakeAi({ 'abcdefghij' });
|
||||||
|
local chatBox = fakeChatBox();
|
||||||
|
local bot = createTrapGpt({
|
||||||
|
ai = ai,
|
||||||
|
chatBox = chatBox,
|
||||||
|
settings = fakeSettings({
|
||||||
|
['trapgpt.throttle_seconds'] = 0,
|
||||||
|
['trapgpt.max_reply_chars'] = 6,
|
||||||
|
}),
|
||||||
|
sleep = function() end,
|
||||||
|
now = function() return 60; end,
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.onChat('alice', 'one', nil, false);
|
||||||
|
bot.processOnce();
|
||||||
|
testlib.assertEquals(chatBox.messages[1].message, 'abc...');
|
||||||
|
end);
|
||||||
|
|
||||||
|
testlib.run();
|
||||||
Loading…
Reference in New Issue
Block a user