|
|
@@ -10,19 +10,78 @@
|
|
|
const suggestions = root.find('.global-assistant-suggestions');
|
|
|
const input = root.find('.global-assistant-input');
|
|
|
const sendButton = root.find('.global-assistant-send');
|
|
|
+ const entry = root.find('.global-assistant-entry');
|
|
|
const projectId = root.data('project-id');
|
|
|
const storageKey = 'global-assistant-history-v2-' + projectId + '-' + userID;
|
|
|
+ const entryPositionKey = 'global-assistant-entry-position-' + userID;
|
|
|
let history = loadHistory();
|
|
|
let conversationId = getConversationId();
|
|
|
let sending = false;
|
|
|
+ let entryPosition = loadEntryPosition();
|
|
|
+ let entryDrag = null;
|
|
|
+ let suppressEntryClick = false;
|
|
|
|
|
|
- root.find('.global-assistant-entry').on('click', function() {
|
|
|
+ applyEntryPosition();
|
|
|
+
|
|
|
+ entry.on('click', function(event) {
|
|
|
+ if (suppressEntryClick) {
|
|
|
+ event.preventDefault();
|
|
|
+ suppressEntryClick = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
panel.addClass('show').attr('aria-hidden', 'false');
|
|
|
$(this).hide();
|
|
|
input.trigger('focus');
|
|
|
scrollToBottom();
|
|
|
});
|
|
|
|
|
|
+ entry.on('pointerdown', function(event) {
|
|
|
+ const pointer = event.originalEvent;
|
|
|
+ if (pointer.pointerType === 'mouse' && pointer.button !== 0) return;
|
|
|
+ entryDrag = {
|
|
|
+ pointerId: pointer.pointerId,
|
|
|
+ startX: pointer.clientX,
|
|
|
+ startY: pointer.clientY,
|
|
|
+ positionX: entryPosition.x,
|
|
|
+ positionY: entryPosition.y,
|
|
|
+ moved: false,
|
|
|
+ };
|
|
|
+ suppressEntryClick = false;
|
|
|
+ this.setPointerCapture(pointer.pointerId);
|
|
|
+ entry.addClass('dragging');
|
|
|
+ event.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ entry.on('pointermove', function(event) {
|
|
|
+ const pointer = event.originalEvent;
|
|
|
+ if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
|
|
|
+ const offsetX = entryDrag.startX - pointer.clientX;
|
|
|
+ const offsetY = entryDrag.startY - pointer.clientY;
|
|
|
+ if (Math.abs(offsetX) > 3 || Math.abs(offsetY) > 3) entryDrag.moved = true;
|
|
|
+ entryPosition = clampEntryPosition({
|
|
|
+ x: entryDrag.positionX + offsetX,
|
|
|
+ y: entryDrag.positionY + offsetY,
|
|
|
+ });
|
|
|
+ applyEntryPosition();
|
|
|
+ event.preventDefault();
|
|
|
+ });
|
|
|
+
|
|
|
+ entry.on('pointerup pointercancel', function(event) {
|
|
|
+ const pointer = event.originalEvent;
|
|
|
+ if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
|
|
|
+ suppressEntryClick = event.type === 'pointerup' && entryDrag.moved;
|
|
|
+ if (this.hasPointerCapture(pointer.pointerId)) this.releasePointerCapture(pointer.pointerId);
|
|
|
+ entryDrag = null;
|
|
|
+ entry.removeClass('dragging');
|
|
|
+ saveEntryPosition();
|
|
|
+ });
|
|
|
+
|
|
|
+ $(window).on('resize.globalAssistant', function() {
|
|
|
+ entryPosition = clampEntryPosition(entryPosition);
|
|
|
+ applyEntryPosition();
|
|
|
+ saveEntryPosition();
|
|
|
+ });
|
|
|
+
|
|
|
root.find('.global-assistant-close').on('click', function() {
|
|
|
panel.removeClass('show').attr('aria-hidden', 'true');
|
|
|
root.find('.global-assistant-entry').show();
|
|
|
@@ -221,5 +280,37 @@
|
|
|
const last = history.slice().reverse().find(item => item.role === 'assistant' && item.data);
|
|
|
return last && last.data.conversationId ? last.data.conversationId : '';
|
|
|
}
|
|
|
+
|
|
|
+ function loadEntryPosition() {
|
|
|
+ try {
|
|
|
+ const value = JSON.parse(localStorage.getItem(entryPositionKey) || '{}');
|
|
|
+ return clampEntryPosition({ x: Number(value.x) || 0, y: Number(value.y) || 0 });
|
|
|
+ } catch (err) {
|
|
|
+ return { x: 0, y: 0 };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function saveEntryPosition() {
|
|
|
+ try {
|
|
|
+ localStorage.setItem(entryPositionKey, JSON.stringify(entryPosition));
|
|
|
+ } catch (err) {
|
|
|
+ // 浏览器禁用本地存储时仅在当前页面保留按钮位置。
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyEntryPosition() {
|
|
|
+ entry.css('transform', `translate(${-entryPosition.x}px, ${-entryPosition.y}px)`);
|
|
|
+ }
|
|
|
+
|
|
|
+ function clampEntryPosition(position) {
|
|
|
+ const panelWidth = parseFloat(panel.css('width')) || 420;
|
|
|
+ const panelHeight = Math.min(650, Math.max(0, window.innerHeight - 48));
|
|
|
+ const maxX = Math.max(0, panelWidth - entry.outerWidth());
|
|
|
+ const maxY = Math.max(0, panelHeight - entry.outerHeight());
|
|
|
+ return {
|
|
|
+ x: Math.min(maxX, Math.max(0, Number(position.x) || 0)),
|
|
|
+ y: Math.min(maxY, Math.max(0, Number(position.y) || 0)),
|
|
|
+ };
|
|
|
+ }
|
|
|
});
|
|
|
})();
|