488 lines
13 KiB
JavaScript
488 lines
13 KiB
JavaScript
if(!window.aiBrowseMiniProgram) window.aiBrowseMiniProgram = {};
|
|
|
|
let firstGenerate = true;
|
|
let messageList = [];
|
|
const fieldArr = Array.from(Object.values(fields));
|
|
|
|
/**
|
|
* Format datetime.
|
|
*
|
|
* @param {Date?} date
|
|
* @returns {string}
|
|
*/
|
|
function formatDateTime(date = new Date())
|
|
{
|
|
const year = date.getFullYear();
|
|
const month = date.getMonth() + 1;
|
|
const day = date.getDate();
|
|
const hours = date.getHours();
|
|
const minutes = date.getMinutes();
|
|
|
|
const formattedDate = `${year}/${month}/${day}`;
|
|
const formattedTime = `${hours}:${minutes < 10 ? '0' + minutes : minutes}`;
|
|
|
|
return `${formattedDate} ${formattedTime}`;
|
|
}
|
|
|
|
/**
|
|
* Get form value map.
|
|
*
|
|
* @returns {Map<string, string>}
|
|
*/
|
|
function getFormValue()
|
|
{
|
|
/**
|
|
* @type {Map<string, string>}
|
|
*/
|
|
const fieldValueMap = new Map();
|
|
fieldArr.forEach(field =>
|
|
{
|
|
const {id, name} = field;
|
|
let $field = $(`[data-name="${name}"]`);
|
|
if(!$field.length) $field = $(`[name="field-${id}"]`);
|
|
if(!$field.hasClass('picker-box')) fieldValueMap.set(name, $field.prop('value'));
|
|
else fieldValueMap.set(name, $field.zui('picker').$.value);
|
|
});
|
|
return fieldValueMap;
|
|
}
|
|
|
|
/**
|
|
* Get required fields.
|
|
*
|
|
* @returns {string[]}
|
|
*/
|
|
function getRequiredFields()
|
|
{
|
|
return fieldArr
|
|
.filter(field => field.required === '1')
|
|
.map(field => field.name);
|
|
}
|
|
|
|
/**
|
|
* Generate prompt string.
|
|
*
|
|
* @param {Map<string, string>} fieldArr
|
|
* @returns {string}
|
|
*/
|
|
function generatePrompt(fieldArr)
|
|
{
|
|
let promptStr = prompt;
|
|
fieldArr.forEach((value, key) =>
|
|
{
|
|
promptStr = promptStr.replace(new RegExp(`\\s<${key}>\\s`, 'g'), value);
|
|
promptStr = promptStr.replace(new RegExp(`\\s<${key}>\\s`, 'g'), value);
|
|
});
|
|
return promptStr;
|
|
}
|
|
|
|
/**
|
|
* Check required fields.
|
|
*
|
|
* @param {string[]} requiredFieldNames
|
|
* @param {Map<string, string>} fieldArr
|
|
* @returns {true|string}
|
|
*/
|
|
function checkRequiredFields(requiredFieldNames, fieldArr)
|
|
{
|
|
for(const name of requiredFieldNames)
|
|
{
|
|
if(!fieldArr.has(name) || !fieldArr.get(name)) return name;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Clear error tip.
|
|
*/
|
|
function clearErrorTip()
|
|
{
|
|
$('.form-container').find('.has-error').removeClass('has-error');
|
|
$('.form-container').find('.form-tip').remove();
|
|
}
|
|
|
|
/**
|
|
* Send message to ai.
|
|
*
|
|
* @param {string} message
|
|
*/
|
|
function sendMessagesToAI(message)
|
|
{
|
|
const curMsgObj = {
|
|
role: 'user',
|
|
content: message,
|
|
time: (new Date()).toLocaleString()
|
|
};
|
|
|
|
const formData = new FormData();
|
|
formData.set('message', message);
|
|
if(messageList.length) formData.set('messages', messageList);
|
|
|
|
const $inputBox = $('.chat-input-box');
|
|
const $sendBtn = $('.send-btn');
|
|
const $messageList = $('.chat-history .message-list');
|
|
const $generateBtn = $('.generate-btn');
|
|
$inputBox.attr('readonly', 'readonly');
|
|
$sendBtn.attr('disabled', 'disabled');
|
|
$generateBtn.attr('disabled', 'disabled');
|
|
$.post(
|
|
postLink,
|
|
formData,
|
|
function (response)
|
|
{
|
|
$inputBox.removeAttr('readonly');
|
|
$sendBtn.removeAttr('disabled');
|
|
$generateBtn.removeAttr('disabled');
|
|
response = JSON.parse(response);
|
|
const {message, result, reason} = response;
|
|
if(result === 'success')
|
|
{
|
|
const {content, time} = message;
|
|
const $message = createMessage('ai', content, time);
|
|
$messageList.find('.tmp-message').remove();
|
|
$messageList.append($message);
|
|
$messageList[0].scrollTo(0, $messageList[0].scrollHeight);
|
|
|
|
messageList.append(curMsgObj);
|
|
messageList.append(message);
|
|
return;
|
|
}
|
|
|
|
if(reason === 'no model')
|
|
{
|
|
$('.chat').addClass('hidden');
|
|
$('.chat-nomodel').removeClass('hidden');
|
|
$('form .footer .btn.primary').attr('disabled', 'disabled');
|
|
return;
|
|
}
|
|
if(reason === 'unpublished')
|
|
{
|
|
$('#open-dialog').trigger('click')
|
|
return;
|
|
}
|
|
if(reason === 'no response')
|
|
{
|
|
const $message = createMessage('ai', message);
|
|
$messageList.find('.tmp-message').remove();
|
|
$messageList.append($message);
|
|
$messageList[0].scrollTo(0, $messageList[0].scrollHeight);
|
|
return;
|
|
}
|
|
}
|
|
);
|
|
|
|
const $message = createMessage('user', message);
|
|
$messageList.append($message);
|
|
$messageList.append(createTmpMessage());
|
|
$messageList[0].scrollTo(0, $messageList[0].scrollHeight);
|
|
}
|
|
|
|
window.aiBrowseMiniProgram.startAIChat = function()
|
|
{
|
|
clearErrorTip();
|
|
const fieldArr = getFormValue();
|
|
const requiredFieldNames = getRequiredFields();
|
|
const result = checkRequiredFields(requiredFieldNames, fieldArr);
|
|
if(typeof result === 'string')
|
|
{
|
|
const $container = $('.form-container').find(`[data-name="${result}"]`);
|
|
const fid = $container.attr('data-fid');
|
|
const $formGroup = $container.closest('.form-group');
|
|
$formGroup
|
|
.attr('name', `field-${fid}`)
|
|
.addClass('has-error')
|
|
.append(`<div class="form-tip" id="field-${fid}Tip">${emptyNameWarning.replace('%s', result)}</div>`);
|
|
return;
|
|
}
|
|
|
|
const promptStr = generatePrompt(fieldArr);
|
|
if(firstGenerate)
|
|
{
|
|
$(this).text(regenerateLang);
|
|
firstGenerate = false;
|
|
}
|
|
else
|
|
{
|
|
const $messageList = $('.chat-history .message-list');
|
|
$messageList.append(createLocalNotification(`${formatDateTime()} ${clearContextLang}`));
|
|
messageList = [];
|
|
}
|
|
$('.chat').addClass('hidden');
|
|
$('.chat-history').removeClass('hidden');
|
|
$('.input-container').removeClass('hidden');
|
|
sendMessagesToAI(promptStr);
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.handleStarBtnClick = function()
|
|
{
|
|
const $btn = $(this);
|
|
const url = $btn.attr('data-url');
|
|
$.get(url, function (response)
|
|
{
|
|
if(response.status == '1')
|
|
{
|
|
$btn.children('img').attr('src', 'static/svg/star.svg');
|
|
$btn.attr('data-url', $btn.attr('data-url').replace('false', 'true'));
|
|
}
|
|
else
|
|
{
|
|
$btn.children('img').attr('src', 'static/svg/star-empty.svg');
|
|
$btn.attr('data-url', $btn.attr('data-url').replace('true', 'false'));
|
|
}
|
|
}, 'json');
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.handleRestBtnClick = function()
|
|
{
|
|
try
|
|
{
|
|
$('.form-container .picker-box').each(function()
|
|
{
|
|
$(this).zui('picker').$.clear();
|
|
});
|
|
$('.form-container .form-group .form-field').val('');
|
|
}
|
|
catch (error) {}
|
|
};
|
|
|
|
let composing = false;
|
|
|
|
/**
|
|
* Handle input box enter.
|
|
*
|
|
* @param {KeyboardEvent} event
|
|
*/
|
|
window.aiBrowseMiniProgram.handleInputEnter = function(event)
|
|
{
|
|
if(event.code === 'Enter' && !composing)
|
|
{
|
|
event.preventDefault();
|
|
window.aiBrowseMiniProgram.clearInputAndChat();
|
|
}
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.clearInputAndChat = function()
|
|
{
|
|
const $inputBox = $('.chat-input-box');
|
|
const message = $inputBox.val()
|
|
$inputBox.val('');
|
|
if(!message) return;
|
|
sendMessagesToAI(message);
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.handleInputCompositionStart = function()
|
|
{
|
|
composing = true;
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.handleInputCompositionEnd = function()
|
|
{
|
|
composing = false;
|
|
};
|
|
|
|
/**
|
|
* Rest input box height.
|
|
*
|
|
* @param {Event} event
|
|
*/
|
|
window.aiBrowseMiniProgram.resetInputHeight = function(event)
|
|
{
|
|
/**
|
|
* @type {HTMLTextAreaElement}
|
|
*/
|
|
const inputBox = event.target;
|
|
inputBox.style.height = 'auto';
|
|
inputBox.style.height = `${(inputBox.scrollHeight + 2)}px`
|
|
};
|
|
|
|
window.aiBrowseMiniProgram.backToSquare = function()
|
|
{
|
|
window.location.href = $.createLink('aiapp', 'square');
|
|
};
|
|
|
|
/**
|
|
* Create message avatar.
|
|
*
|
|
* @param {'ai'|'user'} role
|
|
* @returns {jQuery}
|
|
*/
|
|
function createAvatar(role)
|
|
{
|
|
const $avatar = role === 'user'
|
|
? $('#userMenu-toggle > div.avatar').clone()
|
|
: $('#program-avatar').clone().removeAttr('id');
|
|
if(role === 'ai')
|
|
{
|
|
$avatar.find('svg')
|
|
.attr('height', '14px')
|
|
.attr('width', '14px')
|
|
}
|
|
$avatar.addClass('message-avatar');
|
|
return $avatar;
|
|
}
|
|
|
|
/**
|
|
* Create message time.
|
|
*
|
|
* @param {string} time
|
|
* @returns {jQuery}
|
|
*/
|
|
function createMessageTime(time)
|
|
{
|
|
return $(`<div class="message-time">${time}</div>`);
|
|
}
|
|
|
|
/**
|
|
* Create message content.
|
|
*
|
|
* @param {'user'|'ai'} role
|
|
* @param {string} content
|
|
* @returns {jQuery}
|
|
*/
|
|
function createMessageContent(role, content)
|
|
{
|
|
content = content.replace(/\n{2,}/g, '\n');
|
|
return $(`<pre class="message-content ${role}-message-content">${content}</pre>`);
|
|
}
|
|
|
|
/**
|
|
* Create message body.
|
|
*
|
|
* @param {'user'|'ai'} role
|
|
* @param {string} content
|
|
* @param {string} time
|
|
* @returns {jQuery}
|
|
*/
|
|
function createMessageBody(role, content, time)
|
|
{
|
|
const $time = createMessageTime(time);
|
|
const $content = createMessageContent(role, content);
|
|
return $(`<div class="message-body"></div>`)
|
|
.append($time)
|
|
.append($content);
|
|
}
|
|
|
|
function createTmpMessage()
|
|
{
|
|
const $avatar = createAvatar('ai');
|
|
const $body = $(`<div class="message-body flex-1">
|
|
<div class="animate-pulse">
|
|
<div class="h-3 w-24 loading-bar mb-1.5 rounded"></div>
|
|
</div>
|
|
<div class="message-content ai-message-content w-full">
|
|
<div class="animate-pulse w-full">
|
|
<div class="h-3 w-full loading-bar mb-2 rounded"></div>
|
|
<div class="h-3 w-full loading-bar mb-2 rounded"></div>
|
|
<div class="h-3 w-1/2 loading-bar rounded"></div>
|
|
</div>
|
|
</div>
|
|
</div>`);
|
|
return $(`<div class="message ai-message tmp-message"></div>`)
|
|
.append($avatar)
|
|
.append($body);
|
|
}
|
|
|
|
/**
|
|
* Create message.
|
|
*
|
|
* @param {'user'|'ai'} role
|
|
* @param {string} content
|
|
* @param {string} time
|
|
* @returns {jQuery}
|
|
*/
|
|
function createMessage(role, content, time = formatDateTime())
|
|
{
|
|
const $avatar = createAvatar(role);
|
|
const $body = createMessageBody(role, content, time);
|
|
return $(`<div class="message ${role}-message"></div>`)
|
|
.append($avatar)
|
|
.append($body);
|
|
}
|
|
|
|
/**
|
|
* Create local notification.
|
|
*
|
|
* @param {string} content
|
|
* @returns {jQuery}
|
|
*/
|
|
function createLocalNotification(content)
|
|
{
|
|
return $(`<div class="local-notification"><span>${content}</span></div>`);
|
|
}
|
|
|
|
/**
|
|
* Create server notification.
|
|
*
|
|
* @param {string} content
|
|
* @returns {jQuery}
|
|
*/
|
|
function createServerNotification(content)
|
|
{
|
|
return $(`<div class="server-notification">${content}</div>`);
|
|
}
|
|
|
|
/**
|
|
* Append current path.
|
|
*/
|
|
function appendCurrentPath()
|
|
{
|
|
$('#heading .toolbar')
|
|
.append(`<i class="icon icon-angle-right"></i>`)
|
|
.append(`<span class="ml-2">${pathname}</span>`);
|
|
}
|
|
|
|
$(function()
|
|
{
|
|
appendCurrentPath();
|
|
$('#to-language-model').prop('href', $.createLink('ai', 'models'));
|
|
$('#reload-current').on('click', () =>
|
|
{
|
|
loadCurrentPage('.mini-program');
|
|
});
|
|
|
|
if(messages && messages.length)
|
|
{
|
|
const $messageList = $('.chat-history .message-list');
|
|
const messagesReverse = messages.reverse();
|
|
for(let i = 0; i < messagesReverse.length; i++)
|
|
{
|
|
const message = messagesReverse[i];
|
|
const {type, content, createdDate: time} = message;
|
|
let role;
|
|
if(type === 'req') role = 'user';
|
|
else if(type === 'res') role = 'ai';
|
|
const $message = (role === 'user' || role === 'ai')
|
|
? createMessage(role, content, time)
|
|
: createServerNotification(content);
|
|
$messageList.append($message);
|
|
}
|
|
|
|
$messageList.append(createLocalNotification(`${formatDateTime()} ${newChatTip}`));
|
|
$messageList[0].scrollTo(0, $messageList[0].scrollHeight);
|
|
}
|
|
|
|
if(isAppDisabled)
|
|
{
|
|
$('#open-dialog').trigger('click')
|
|
}
|
|
|
|
const $inputBox = $('.chat-input-box');
|
|
if(!$inputBox.attr('data-bind'))
|
|
{
|
|
$inputBox
|
|
.on('input', window.aiBrowseMiniProgram.resetInputHeight)
|
|
.on('keydown', window.aiBrowseMiniProgram.handleInputEnter)
|
|
.on('compositionstart', window.aiBrowseMiniProgram.handleInputCompositionStart)
|
|
.on('compositionend', window.aiBrowseMiniProgram.handleInputCompositionEnd);
|
|
|
|
$inputBox.attr('data-bind', 'true');
|
|
}
|
|
|
|
if(!hasModels)
|
|
{
|
|
$('.chat').addClass('hidden');
|
|
$('.chat-nomodel').removeClass('hidden');
|
|
$('form .footer .btn.primary').attr('disabled', 'disabled');
|
|
$('.generate-btn').attr('disabled', 'disabled');
|
|
}
|
|
});
|