OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
{{-- OneBot 界面内 AI 助手:右下角悬浮聊天窗 --}}
@if(session()->has('userdata'))
<style>
#onebotAiFab {
position: fixed; right: 22px; bottom: 22px; z-index: 9998;
width: 56px; height: 56px; border-radius: 50%;
background: #4f46e5; color: #fff; border: none; cursor: pointer;
box-shadow: 0 6px 20px rgba(0,0,0,0.25);
font-size: 24px; display: flex; align-items: center; justify-content: center;
}
#onebotAiFab:hover { background: #4338ca; }
#onebotAiPanel {
position: fixed; right: 22px; bottom: 90px; z-index: 9999;
width: 420px; min-width: 320px; max-width: 94vw; height: 560px; min-height: 320px; max-height: 90vh;
background: #fff; border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,0.28);
display: none; flex-direction: column; overflow: hidden;
}
#onebotAiPanel.open { display: flex; }
#onebotAiHeader {
padding: 12px 16px; background: #4f46e5; color: #fff;
display: flex; justify-content: space-between; align-items: center; flex: none;
}
#onebotAiHeader .title { font-weight: 600; font-size: 15px; }
#onebotAiHeader .cfg { cursor: pointer; opacity: .85; font-size: 13px; }
#onebotAiHeader .cfg:hover { opacity: 1; }
#onebotAiHeader .close { cursor: pointer; font-size: 18px; line-height: 1; opacity: .85; }
#onebotAiHeader .close:hover { opacity: 1; }
#onebotAiMessages {
flex: 1; overflow-y: auto; padding: 14px; background: #f8fafc;
display: flex; flex-direction: column; gap: 10px;
}
#onebotAiMessages .msg { max-width: 82%; padding: 9px 12px; border-radius: 10px; font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
#onebotAiMessages .msg.user { align-self: flex-end; background: #4f46e5; color: #fff; border-bottom-right-radius: 3px; }
#onebotAiMessages .msg.ai { align-self: flex-start; background: #fff; border: 1px solid #e2e8f0; color: #0f172a; border-bottom-left-radius: 3px; }
#onebotAiMessages .msg.err { align-self: flex-start; background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c; }
#onebotAiInput {
flex: none; border-top: 1px solid #e2e8f0; padding: 10px; background: #fff;
display: flex; gap: 8px; align-items: flex-end;
}
#onebotAiInput textarea {
flex: 1; resize: vertical; min-height: 42px; max-height: 260px;
border: 1px solid #cbd5e1; border-radius: 8px;
padding: 8px 10px; font-size: 13px; height: 42px; outline: none;
line-height: 1.5;
}
#onebotAiInput textarea:focus { border-color: #4f46e5; }
#onebotAiInput button {
background: #4f46e5; color: #fff; border: none; border-radius: 8px;
padding: 0 16px; font-size: 13px; cursor: pointer; flex: none; height: 42px;
}
#onebotAiInput button:hover { background: #4338ca; }
#onebotAiInput button:disabled { background: #a5b4fc; cursor: not-allowed; }
#onebotAiResize {
position: absolute; right: 0; bottom: 0; width: 16px; height: 16px;
cursor: nwse-resize; z-index: 10000;
background: linear-gradient(135deg, transparent 0 45%, #cbd5e1 45% 55%, transparent 55% 100%);
}
</style>
<button id="onebotAiFab" title="AI 助手"><span class="fa fa-robot"></span></button>
<div id="onebotAiPanel">
<div id="onebotAiHeader">
<span class="title"><span class="fa fa-robot"></span> AI 助手</span>
<span>
<span class="cfg" id="onebotAiCfg" title="AI 设置"><span class="fa fa-gear"></span></span>
<span class="close" id="onebotAiClose" title="关闭">&times;</span>
</span>
</div>
<div id="onebotAiMessages"></div>
<div id="onebotAiInput">
<textarea id="onebotAiText" placeholder="输入指令,例如:列出所有 BOM" rows="1"></textarea>
<button id="onebotAiSend">发送</button>
</div>
<div id="onebotAiResize" title="拖动调整大小"></div>
</div>
<script>
jQuery(document).ready(function () {
var BASE = '{{ BASE_URL }}';
var csrf = function () { return jQuery('meta[name="csrf-token"]').attr('content') || ''; };
var history = []; // [{role, content}]
var $fab = jQuery('#onebotAiFab');
var $panel = jQuery('#onebotAiPanel');
var $msgs = jQuery('#onebotAiMessages');
var $text = jQuery('#onebotAiText');
var $send = jQuery('#onebotAiSend');
function addMsg(role, content) {
var cls = role === 'user' ? 'user' : (role === 'err' ? 'err' : 'ai');
jQuery('<div class="msg ' + cls + '"></div>').text(content).appendTo($msgs);
$msgs.scrollTop($msgs[0].scrollHeight);
}
function send() {
var text = $text.val().trim();
if (!text) { return; }
addMsg('user', text);
history.push({ role: 'user', content: text });
$text.val('');
$send.prop('disabled', true);
addMsg('ai', '思考中…');
jQuery.ajax({
url: BASE + '/ai/chat',
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrf() },
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ messages: history })
}).done(function (res) {
jQuery('#onebotAiMessages .msg:last').remove();
var reply = res.content || '';
addMsg('ai', reply);
history.push({ role: 'assistant', content: reply });
}).fail(function (xhr) {
jQuery('#onebotAiMessages .msg:last').remove();
var m = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : ('HTTP ' + xhr.status);
addMsg('err', m);
}).always(function () {
$send.prop('disabled', false);
$text.trigger('focus');
});
}
$fab.on('click', function () { $panel.toggleClass('open'); if ($panel.hasClass('open')) $text.trigger('focus'); });
jQuery('#onebotAiClose').on('click', function () { $panel.removeClass('open'); });
$send.on('click', send);
$text.on('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } });
// 拖动调整面板大小
(function () {
var $resize = jQuery('#onebotAiResize');
var startX, startY, startW, startH;
$resize.on('mousedown', function (e) {
e.preventDefault();
e.stopPropagation();
startX = e.clientX;
startY = e.clientY;
startW = $panel.width();
startH = $panel.height();
jQuery(document).on('mousemove.aiResize', function (e) {
var w = startW + (e.clientX - startX);
var h = startH + (e.clientY - startY);
// 应用 min/max 约束(与 CSS 一致)
w = Math.max(320, Math.min(w, window.innerWidth * 0.94));
h = Math.max(320, Math.min(h, window.innerHeight * 0.9));
$panel.css({ width: w + 'px', height: h + 'px' });
});
jQuery(document).on('mouseup.aiResize', function () {
jQuery(document).off('mousemove.aiResize mouseup.aiResize');
});
});
// 触屏支持
$resize.on('touchstart', function (e) {
var t = e.originalEvent.touches[0];
startX = t.clientX;
startY = t.clientY;
startW = $panel.width();
startH = $panel.height();
});
$resize.on('touchmove', function (e) {
e.preventDefault();
var t = e.originalEvent.touches[0];
var w = startW + (t.clientX - startX);
var h = startH + (t.clientY - startY);
w = Math.max(320, Math.min(w, window.innerWidth * 0.94));
h = Math.max(320, Math.min(h, window.innerHeight * 0.9));
$panel.css({ width: w + 'px', height: h + 'px' });
});
})();
// AI 设置:弹出配置(预留地址填写)
jQuery('#onebotAiCfg').on('click', function () {
jQuery.ajax({
url: BASE + '/ai/config',
method: 'GET',
dataType: 'json'
}).done(function (res) {
var d = res.data || {};
addMsg('ai', '当前 AI 配置:\nProvider: ' + (d.provider || '-') +
'\nBaseURL: ' + (d.baseUrl || '-') +
'\nModel: ' + (d.model || '-') +
'\nAPI Key: ' + (d.apiKeyMasked || '未配置') +
'\n\n配置在 .env 的 AI_* 变量,或联系管理员填写)');
}).fail(function () {
addMsg('err', '读取 AI 配置失败');
});
});
});
</script>
@endif