+ revive xuan web chat.

This commit is contained in:
liwenrui
2024-05-08 17:30:57 +08:00
parent fb52ad1cb1
commit 9f25b6f744
3 changed files with 197 additions and 29 deletions
@@ -1,11 +1,5 @@
<?php
use function zin\jsVar;
if(strpos($_SERVER['HTTP_USER_AGENT'], 'xuanxuan') != false): ?>
<?php if(strpos($_SERVER['HTTP_USER_AGENT'], 'xuanxuan') != false): ?>
<?php global $app; $app->setClientTheme('blue'); ?>
<style>
</style>
<script>
$('html').addClass('xxc-embed');
@@ -22,7 +16,6 @@ function updateAppMenu()
});
window.open('xxc://extension.zentao-integrated.updateAppMenu/' + encodeURIComponent(JSON.stringify(menuItems)));
}
updateAppMenu();
$(document).on('showapp', function(e, app)
@@ -34,4 +27,81 @@ $(document).on('hideapp', function(e, app)
window.open('xxc://extension.zentao-integrated.hideAppMenuItem/' + encodeURIComponent(JSON.stringify({id: app.code, openedApps:Object.keys($.apps.openedApps)})));
});
</script>
<?php else: ?>
<?php
global $app;
$im = $app->control->loadModel('im');
$xxdStatus = $im->getXxdStatus();
if(isset($app->config->xuanxuan->turnon) && $app->config->xuanxuan->turnon && $xxdStatus == 'online')
{
$xuanConfig = new stdclass();
$token = $im->userGetAuthToken($app->user->id, 'zentaoweb');
$clientUrl = isset($app->config->webClientUrl) ? $app->config->webClientUrl : 'data/xuanxuan/web/index.html';
$backendUrl = $im->getServer('zentao');
$xuanConfig->clientUrl = $clientUrl;
$xuanConfig->server = ($app->config->xuanxuan->https == 'on' ? 'https' : 'http') . '://' . parse_url($backendUrl, PHP_URL_HOST) . ':' . $app->config->xuanxuan->commonPort;
$xuanConfig->account = $app->user->account;
$xuanConfig->authKey = $token->token;
$xuanConfig->debug = $app->config->debug;
}
?>
<?php if(isset($xuanConfig)): ?>
<style>
#xx-embed-container {z-index: 1010!important;}
#xx-embed-container .xx-embed-has-animation {transition: min-width .5s ease-out, min-height .5s ease-out, transform, opacity!important;}
#xx-embed-container .xx-embed {width: 330px; height: 100%; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06)!important;}
#xx-embed-container .xx-embed.xx-embed-hidden,
#xx-embed-container .xx-embed.xx-embed-collapsed {width: 330px!important; height: 100%!important; opacity: .7; pointer-events: none; transform: translateY(100%); display: block!important;}
#xx-embed-container .xx-embed.has-chat-view {min-width: 1000px!important;}
#xx-embed-container .xx-embed-body {min-height: initial!important;}
</style>
<?php js::import($app->getWebRoot() . 'data/xuanxuan/sdk/sdk.min.js'); ?>
<script>
/* Toggle xuan client popover */
function toggleXuanClient()
{
if(!window.xuan.shown) window.xuan.show();
else window.xuan.toggleCollapse();
}
/* Handle chat notice change */
function handleXuanNoticeChange(notice)
{
$('#chatNoticeBadge').toggleClass('show', !!notice.count).text(notice.count);
}
/* Handle client route change */
function handleXuanRouteChange(route)
{
var hasShowChatView = route.indexOf('#/chats/') === 0 && !!route.split('/')[3];
$('#' + window.xuan.id).toggleClass('has-chat-view', hasShowChatView);
}
$(function()
{
const xuanConfig = <?php echo json_encode($xuanConfig); ?>;
if(typeof Xuanxuan !== 'undefined')
{
/* Set client global options*/
Xuanxuan.setGlobalOptions(
{
position: 'right',
width: 280,
preload: true,
showHeader: false,
onNotice: handleXuanNoticeChange,
onRouteChange: handleXuanRouteChange,
lang: '<?php echo $app->getClientLang()?>'
});
/* Create client instance */
window.xuan = new Xuanxuan(xuanConfig);
/* Setup xuan web chat, see zin/wg/chatbtn. */
window.setupXuan();
}
})
</script>
<?php endif; ?>
<?php endif; ?>
+118 -18
View File
@@ -7,39 +7,139 @@ class chatBtn extends wg
public static function getPageCSS(): ?string
{
return <<<CSS
#chat-container {position: fixed; left: 96px; right: 0; height: calc(100% - 40px); display: none;}
#ai-chat-view {position: fixed; right: 0; width: 330px; bottom: 40px; top: 49px; outline: 1px solid; outline-color: rgba(var(--color-border-rgb),var(--tw-border-opacity))}
#chat-container {position: fixed; left: 100px; right: 0; height: calc(100% - 36px); display: none;}
.hide-menu #chat-container {left: 64px;}
#chat-switch {position: absolute; width: 330px; height: 52px; right: 0; background: #fff; display: flex; justify-content: center; align-items: center; border-bottom: 1px solid #eee; z-index: 20;}
.chat-switch-bg {display: flex; justify-content: center; align-items: center; background-color: #eff5ff; border-radius: 16px;}
.chat-switch-item {width: 96px; padding: 4px 0; border-radius: 16px; text-align: center; color: #838a9c; position: relative; user-select: none; cursor: pointer;}
.chat-switch-item:hover {color: #838a9c;}
.chat-switch-item.active {font-weight: bold; color: #fff; background-color: #5999fc;}
.chat-switch-item.has-notice::after {content: ''; position: absolute; right: 26px; top: 4px; width: 6px; height: 6px; border-radius: 50%; background-color: #ff535d;}
#xuan-chat-view {position: absolute; width: 100%; height: 100%; z-index: 10; display: none;}
#xuan-chat-view #xx-embed-container {position: absolute; bottom: 0; left: 0; right: 0; top: 0;}
#ai-chat-view {position: fixed; right: 0; width: 330px; bottom: 36px; top: 49px; outline: 1px solid; outline-color: rgba(var(--color-border-rgb),var(--tw-border-opacity))}
#ai-chat-frame {height: 100%; width: 100%;}
.unconfigured {position: absolute; width: 330px; padding: 20px; right: 0; top: 0; bottom: 0; background: #fff; outline: 1px solid #eee;}
.unconfigured > div {margin-bottom: 10px;}
#xuan-chat-view .unconfigured {top: 50px;}
#reload-ai-chat.disabled {cursor: wait; color: #999!important;}
CSS;
}
public static function getPageJS(): ?string
{
$aiChatURL = createLink('ai', 'chat');
global $app;
$isAIConfigured = $app->control->loadModel('ai')->hasModelsAvailable();
$hasAIChatPriv = commonModel::hasPriv('ai', 'chat');
$xuanUnconfiguredTip = sprintf($app->lang->index->chat->unconfiguredFormat, $app->lang->index->chat->chat, (common::hasPriv('setting', 'xuanxuan') ? sprintf($app->lang->index->chat->goConfigureFormat, helper::createLink('setting', 'xuanxuan'), $app->lang->index->chat->chat) : $app->lang->index->chat->contactAdminForHelp));
$aiUnconfiguredTip = sprintf($app->lang->index->chat->unconfiguredFormat, $app->lang->index->chat->ai, (common::hasPriv('ai', 'models') ? sprintf($app->lang->index->chat->goConfigureFormat, helper::createLink('ai', 'models') . '#app=admin', $app->lang->index->chat->ai) : $app->lang->index->chat->contactAdminForHelp));
$aiUnauthorizedTip = $app->lang->index->chat->unauthorized;
$aiChatURL = createLink('ai', 'chat');
$aiChatView = "<iframe id='ai-chat-frame' src='$aiChatURL' frameborder='no' allowtransparency='true' scrolling='auto' hidefocus></iframe>";
if(!$hasAIChatPriv) $aiChatView = "<div class='unconfigured text-gray'>$aiUnauthorizedTip</div>";
if(!$isAIConfigured) $aiChatView = "<div class='unconfigured text-gray'>$aiUnconfiguredTip</div>";
$chatContainer = <<<HTML
<div id="chat-container">
<div id="ai-chat-view">
<iframe id="ai-chat-frame" src="{$aiChatURL}" frameborder="no" allowtransparency="true" scrolling="auto" hidefocus></iframe>
<div id="chat-switch">
<div class="chat-switch-bg">
<a class="chat-switch-item" data-value="chat">{$app->lang->index->chat->chat}</a>
<a class="chat-switch-item active" data-value="ai">{$app->lang->index->chat->ai}</a>
</div>
</div>
<div id="xuan-chat-view">
<div class="unconfigured text-gray">$xuanUnconfiguredTip</div>
</div>
<div id="ai-chat-view">$aiChatView</div>
</div>
HTML;
$xuanDefaultOpener = ($hasAIChatPriv && $isAIConfigured) ? '' : 'if(window.xuan) $(`#chat-switch .chat-switch-item[data-value="chat"]`).trigger("click");';
return <<<JAVASCRIPT
window.toggleChatContainer = () => {
if(!$('#chat-container').length)
{
$('body').append(`{$chatContainer}`);
$('#ai-chat-frame').on('load', () => {window.toggleChatContainer();});
return;
}
if(window.xuan) window.xuan[window.xuan.shown ? 'expand' : 'show']();
$('#chat-btn').toggleClass('active');
$('#chat-container').toggle();
}
$(document).on('click', e =>
{
if(!$('#chat-container').length || $('#chat-container').prop('style') && $('#chat-container').prop('style').display !== 'block') return;
if($(e.target).closest('#ai-chat-view,#chat-btn').length) return;
window.toggleChatContainer();
});
};
/* Setup xuan web chat. */
window.setupXuan = () => {
/* Move xuan web client into #xuan-chat-view. */
if(window.xuan && !document.querySelector('#xuan-chat-view #xx-embed-container'))
{
document.querySelector('#xuan-chat-view').prepend(document.querySelector('#xx-embed-container'));
/* Switch to xuan chat by default if AI is not available. */
$xuanDefaultOpener
/* Update badge on chat. */
window.xuan._options.onNotice = function(notice)
{
window.handleXuanNoticeChange(notice);
$('.chat-switch-item[data-value="chat"]').toggleClass('has-notice', !!notice.count);
};
/* Set style into xuan frame. */
let tries = 0;
const xuanFrameWaitLoop = setInterval(function()
{
if(document.querySelector('#xx-embed-container iframe'))
{
clearInterval(xuanFrameWaitLoop);
tries = 0;
const chatsViewWaitLoop = setInterval(function()
{
if(document.querySelector('#xx-embed-container iframe').contentDocument.querySelector('.app-chats-menu'))
{
clearInterval(chatsViewWaitLoop);
document.querySelector('#xx-embed-container iframe').contentDocument.querySelector('.app-chats-menu').style.cssText = 'width: 330px !important; top: 48px;';
document.querySelector('#xx-embed-container iframe').contentDocument.querySelector('.app-chats-cache').style.cssText = 'right: 330px !important;';
document.querySelector('#xx-embed-container iframe').contentDocument.querySelector('.app-chats-menu-search').style.cssText = 'padding: 10px 8px !important;';
document.querySelector('#xx-embed-container iframe').contentDocument.querySelector('.app-chats-menu-search .btn:first-child').style.cssText = 'display: none;';
}
else
{
if(++tries > 20) clearInterval(chatsViewWaitLoop);
}
}, 200);
}
else
{
if(++tries > 20) clearInterval(xuanFrameWaitLoop);
}
}, 200);
}
};
(() => {
/* Insert chat views. */
$('body').append(`{$chatContainer}`);
/* Handle switch events. */
$('#chat-switch .chat-switch-item').on('click', e =>
{
const target = $(e.target);
target.addClass('active').siblings().removeClass('active');
$('#ai-chat-view').toggle(target.data('value') === 'ai');
$('#xuan-chat-view').toggle(target.data('value') === 'chat');
});
/* Handle clicking outside. */
$(document).on('click', e =>
{
if(!$('#chat-container').length || $('#chat-container').prop('style') && $('#chat-container').prop('style').display !== 'block') return;
if($(e.target).closest('#chat-switch,#xuan-chat-view>.unconfigured,#ai-chat-view,#chat-btn').length) return;
window.toggleChatContainer();
});
})();
JAVASCRIPT;
}
+1 -3
View File
@@ -17,8 +17,6 @@ else
$scoreNotice = '';
if($config->vision != 'lite') $scoreNotice = $this->loadModel('score')->getNotice();
$shouldShowAIChat = hasPriv('ai', 'chat') && $this->loadModel('ai')->hasModelsAvailable();
jsVar('scoreNotice', $scoreNotice);
jsVar('edition', $config->edition);
jsVar('vision', $config->vision);
@@ -101,7 +99,7 @@ div
setID('appsToolbar'),
setClass('space-x-1'),
hasPriv('search', 'index') ? globalSearch() : null,
$shouldShowAIChat ? chatBtn() : null,
chatBtn(),
item
(
setClass('ghost btn-zentao px-1'),