|
|
@@ -0,0 +1,207 @@
|
|
|
+'use strict';
|
|
|
+
|
|
|
+(function() {
|
|
|
+ $(function() {
|
|
|
+ const root = $('#global-assistant');
|
|
|
+ if (root.length === 0) return;
|
|
|
+
|
|
|
+ const panel = root.find('.global-assistant-panel');
|
|
|
+ const messages = root.find('.global-assistant-messages');
|
|
|
+ const suggestions = root.find('.global-assistant-suggestions');
|
|
|
+ const input = root.find('.global-assistant-input');
|
|
|
+ const sendButton = root.find('.global-assistant-send');
|
|
|
+ const projectId = root.data('project-id');
|
|
|
+ const storageKey = 'global-assistant-history-v2-' + projectId + '-' + userID;
|
|
|
+ let history = loadHistory();
|
|
|
+ let conversationId = getConversationId();
|
|
|
+ let sending = false;
|
|
|
+
|
|
|
+ root.find('.global-assistant-entry').on('click', function() {
|
|
|
+ panel.addClass('show').attr('aria-hidden', 'false');
|
|
|
+ $(this).hide();
|
|
|
+ input.trigger('focus');
|
|
|
+ scrollToBottom();
|
|
|
+ });
|
|
|
+
|
|
|
+ root.find('.global-assistant-close').on('click', function() {
|
|
|
+ panel.removeClass('show').attr('aria-hidden', 'true');
|
|
|
+ root.find('.global-assistant-entry').show();
|
|
|
+ });
|
|
|
+
|
|
|
+ root.find('.global-assistant-clear').on('click', function() {
|
|
|
+ if (sending) return;
|
|
|
+ history = [];
|
|
|
+ conversationId = '';
|
|
|
+ saveHistory();
|
|
|
+ renderHistory();
|
|
|
+ });
|
|
|
+
|
|
|
+ root.find('.global-assistant-form').on('submit', function(event) {
|
|
|
+ event.preventDefault();
|
|
|
+ ask(input.val());
|
|
|
+ });
|
|
|
+
|
|
|
+ input.on('keydown', function(event) {
|
|
|
+ if (event.key === 'Enter' && !event.shiftKey) {
|
|
|
+ event.preventDefault();
|
|
|
+ root.find('.global-assistant-form').trigger('submit');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ suggestions.on('click', 'button', function() {
|
|
|
+ ask($(this).text());
|
|
|
+ });
|
|
|
+
|
|
|
+ renderHistory();
|
|
|
+
|
|
|
+ function ask(question) {
|
|
|
+ question = $.trim(question || '');
|
|
|
+ if (!question || sending) return;
|
|
|
+ input.val('');
|
|
|
+ addHistory({ role: 'user', text: question });
|
|
|
+ setSending(true);
|
|
|
+
|
|
|
+ postData('/assistant/query/v2', {
|
|
|
+ question,
|
|
|
+ conversationId,
|
|
|
+ context: {
|
|
|
+ path: window.location.pathname,
|
|
|
+ title: document.title,
|
|
|
+ subProjectId: root.data('sub-project-id') || '',
|
|
|
+ tenderId: getTenderId(),
|
|
|
+ },
|
|
|
+ }, function(data) {
|
|
|
+ conversationId = data.conversationId || '';
|
|
|
+ addHistory({ role: 'assistant', text: data.answer, data });
|
|
|
+ setSending(false);
|
|
|
+ }, function(message) {
|
|
|
+ addHistory({ role: 'assistant', text: message || '查询失败,请稍后重试。' });
|
|
|
+ setSending(false);
|
|
|
+ }, false, '助手查询失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ function setSending(value) {
|
|
|
+ sending = value;
|
|
|
+ input.prop('disabled', value);
|
|
|
+ sendButton.prop('disabled', value);
|
|
|
+ root.find('.global-assistant-clear').prop('disabled', value);
|
|
|
+ messages.find('.global-assistant-loading').remove();
|
|
|
+ if (value) {
|
|
|
+ const loading = $('<div class="global-assistant-message assistant global-assistant-loading"></div>');
|
|
|
+ loading.append($('<div class="global-assistant-bubble"></div>').text('正在查找相关内容…'));
|
|
|
+ messages.append(loading);
|
|
|
+ }
|
|
|
+ scrollToBottom();
|
|
|
+ if (!value) input.trigger('focus');
|
|
|
+ }
|
|
|
+
|
|
|
+ function addHistory(message) {
|
|
|
+ history.push(message);
|
|
|
+ if (history.length > 20) history = history.slice(history.length - 20);
|
|
|
+ saveHistory();
|
|
|
+ renderHistory();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderHistory() {
|
|
|
+ messages.empty();
|
|
|
+ if (history.length === 0) {
|
|
|
+ appendMessage({
|
|
|
+ role: 'assistant',
|
|
|
+ text: '你好,我可以查询当前账号有权查看的业务数据,也可以从用户手册中查找操作说明。',
|
|
|
+ });
|
|
|
+ renderSuggestions(['查询平台概况', '查询项目', '如何创建标段', '怎么设置审批流程']);
|
|
|
+ } else {
|
|
|
+ history.forEach(appendMessage);
|
|
|
+ const lastAssistant = history.slice().reverse().find(item => item.role === 'assistant' && item.data);
|
|
|
+ renderSuggestions(lastAssistant && lastAssistant.data.suggestions ? lastAssistant.data.suggestions : []);
|
|
|
+ }
|
|
|
+ scrollToBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ function appendMessage(message) {
|
|
|
+ const wrapper = $('<div class="global-assistant-message"></div>').addClass(message.role);
|
|
|
+ const bubble = $('<div class="global-assistant-bubble"></div>').text(message.text || '');
|
|
|
+ wrapper.append(bubble);
|
|
|
+ if (message.role === 'assistant' && message.data) {
|
|
|
+ appendSummary(bubble, message.data.summary || []);
|
|
|
+ appendItems(bubble, message.data.items || []);
|
|
|
+ if (message.data.truncated) {
|
|
|
+ bubble.append($('<div class="global-assistant-more"></div>').text('结果较多,仅展示前20条。请增加名称或编号缩小范围。'));
|
|
|
+ }
|
|
|
+ if (message.data.tenderMatchNotice) {
|
|
|
+ bubble.append($('<div class="global-assistant-tender-notice"></div>').text(message.data.tenderMatchNotice));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ messages.append(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ function appendSummary(bubble, summary) {
|
|
|
+ if (!summary.length) return;
|
|
|
+ const summaryBox = $('<div class="global-assistant-summary"></div>');
|
|
|
+ summary.forEach(function(item) {
|
|
|
+ const block = $('<div></div>');
|
|
|
+ block.append($('<span></span>').text(item.label));
|
|
|
+ block.append($('<strong></strong>').text(item.value));
|
|
|
+ summaryBox.append(block);
|
|
|
+ });
|
|
|
+ bubble.append(summaryBox);
|
|
|
+ }
|
|
|
+
|
|
|
+ function appendItems(bubble, items) {
|
|
|
+ if (!items.length) return;
|
|
|
+ const list = $('<div class="global-assistant-results"></div>');
|
|
|
+ items.forEach(function(item) {
|
|
|
+ const isExternal = /^https?:\/\//i.test(item.url || '');
|
|
|
+ const row = $('<a></a>')
|
|
|
+ .attr('href', item.url || 'javascript:void(0)')
|
|
|
+ .attr('target', isExternal ? '_blank' : '_self');
|
|
|
+ if (isExternal) row.attr('rel', 'noopener noreferrer');
|
|
|
+ const info = $('<div></div>');
|
|
|
+ info.append($('<strong></strong>').text(item.title || '未命名记录'));
|
|
|
+ if (item.description) info.append($('<small></small>').text(item.description));
|
|
|
+ row.append(info);
|
|
|
+ if (item.value) row.append($('<span></span>').text(item.value));
|
|
|
+ list.append(row);
|
|
|
+ });
|
|
|
+ bubble.append(list);
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderSuggestions(list) {
|
|
|
+ suggestions.empty();
|
|
|
+ list.slice(0, 4).forEach(function(text) {
|
|
|
+ suggestions.append($('<button type="button"></button>').text(text));
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function getTenderId() {
|
|
|
+ const match = window.location.pathname.match(/^\/tender\/(\d+)/);
|
|
|
+ return match ? match[1] : '';
|
|
|
+ }
|
|
|
+
|
|
|
+ function scrollToBottom() {
|
|
|
+ messages.scrollTop(messages.prop('scrollHeight'));
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadHistory() {
|
|
|
+ try {
|
|
|
+ const value = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
|
|
+ return value instanceof Array ? value : [];
|
|
|
+ } catch (err) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function saveHistory() {
|
|
|
+ try {
|
|
|
+ localStorage.setItem(storageKey, JSON.stringify(history));
|
|
|
+ } catch (err) {
|
|
|
+ // 浏览器禁用本地存储时仅保留当前页面内的对话。
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getConversationId() {
|
|
|
+ const last = history.slice().reverse().find(item => item.role === 'assistant' && item.data);
|
|
|
+ return last && last.data.conversationId ? last.data.conversationId : '';
|
|
|
+ }
|
|
|
+ });
|
|
|
+})();
|