feat(ai-assistant): 支持上下键切换历史输入

- 按 ↑ 键切换到上一条历史输入(光标在第一行时生效)
  - 按 ↓ 键切换到下一条历史输入(光标在最后一行时生效)
  - 历史记录使用 IndexedDB 持久化存储,最多保存 50 条
  - 重复输入会移动到末尾而非重复添加
  - 弹窗关闭时自动重置导航状态
This commit is contained in:
kuaifan 2026-01-18 13:20:13 +00:00
parent c65f0276bd
commit 59ad79fa58

View File

@ -254,6 +254,13 @@ export default {
//
editingIndex: -1, // -1
editingValue: '', //
//
inputHistoryList: [], //
inputHistoryIndex: 0, //
inputHistoryCurrent: '', //
inputHistoryCacheKey: 'aiAssistant.inputHistory',
inputHistoryLimit: 50,
}
},
created() {
@ -265,6 +272,7 @@ export default {
mounted() {
emitter.on('openAIAssistant', this.onOpenAIAssistant);
this.loadCachedModel();
this.loadInputHistory();
this.mountFloatButton();
},
beforeDestroy() {
@ -560,16 +568,32 @@ export default {
},
/**
* 输入框键盘事件回车发送Shift+回车换行
* 输入框键盘事件回车发送Shift+回车换行上下键切换历史
* 注意输入法组合输入时如中文候选字不发送
*/
onInputKeydown(e) {
if (!e.shiftKey && !this.isComposing) {
if (this.isComposing) {
return;
}
if (!e.shiftKey) {
if (e.key === 'Enter') {
e.preventDefault();
this.onSubmit();
} else if (e.key === 'Escape' && this.displayMode === 'chat') {
return;
}
if (e.key === 'Escape' && this.displayMode === 'chat') {
this.showModal = false;
return;
}
}
//
if (e.key === 'ArrowUp') {
if (!this.navigateInputHistory('up')) {
e.preventDefault();
}
} else if (e.key === 'ArrowDown') {
if (!this.navigateInputHistory('down')) {
e.preventDefault();
}
}
},
@ -622,6 +646,7 @@ export default {
context,
});
this.persistInputHistory(prompt);
this.startStream(streamKey, responseEntry);
return true;
} catch (error) {
@ -1047,6 +1072,7 @@ export default {
this.pendingAutoSubmit = false;
this.clearAutoSubmitTimer();
this.clearActiveSSEClients();
this.resetInputHistoryNavigation();
this.showModal = false;
this.responses = [];
setTimeout(() => {
@ -1338,6 +1364,132 @@ export default {
return time.format('YYYY-MM-DD HH:mm');
},
// ==================== ====================
/**
* 加载输入历史
*/
async loadInputHistory() {
try {
const history = await $A.IDBValue(this.inputHistoryCacheKey);
if (Array.isArray(history)) {
this.inputHistoryList = history;
} else {
this.inputHistoryList = [];
}
} catch (e) {
this.inputHistoryList = [];
}
this.inputHistoryIndex = this.inputHistoryList.length;
this.inputHistoryCurrent = '';
},
/**
* 保存输入到历史
*/
persistInputHistory(content) {
const trimmed = (content || '').trim();
if (!trimmed) {
return;
}
const history = Array.isArray(this.inputHistoryList) ? [...this.inputHistoryList] : [];
//
if (history[history.length - 1] === trimmed) {
this.inputHistoryIndex = history.length;
this.inputHistoryCurrent = '';
return;
}
//
const existIndex = history.indexOf(trimmed);
if (existIndex !== -1) {
history.splice(existIndex, 1);
}
history.push(trimmed);
//
if (history.length > this.inputHistoryLimit) {
history.splice(0, history.length - this.inputHistoryLimit);
}
this.inputHistoryList = history;
this.inputHistoryIndex = history.length;
this.inputHistoryCurrent = '';
$A.IDBSet(this.inputHistoryCacheKey, history).catch(() => {});
},
/**
* 重置历史导航状态
*/
resetInputHistoryNavigation() {
this.inputHistoryIndex = this.inputHistoryList.length;
this.inputHistoryCurrent = '';
},
/**
* 导航输入历史
* @param {string} direction - 'up' 'down'
* @returns {boolean} - 是否允许默认行为
*/
navigateInputHistory(direction) {
if (!this.inputHistoryList.length) {
return true;
}
const textarea = this.$refs.inputRef?.$el?.querySelector('textarea');
if (!textarea) {
return true;
}
const cursorPos = textarea.selectionStart;
const cursorEnd = textarea.selectionEnd;
const value = this.inputValue || '';
//
if (cursorPos !== cursorEnd) {
return true;
}
if (direction === 'up') {
//
const beforeCursor = value.substring(0, cursorPos);
if (beforeCursor.includes('\n')) {
return true;
}
//
if (this.inputHistoryIndex === this.inputHistoryList.length) {
this.inputHistoryCurrent = value;
}
if (this.inputHistoryIndex > 0) {
this.inputHistoryIndex--;
this.inputValue = this.inputHistoryList[this.inputHistoryIndex] || '';
this.$nextTick(() => {
const ta = this.$refs.inputRef?.$el?.querySelector('textarea');
ta?.setSelectionRange(0, 0);
});
return false;
}
} else if (direction === 'down') {
//
const afterCursor = value.substring(cursorPos);
if (afterCursor.includes('\n')) {
return true;
}
if (this.inputHistoryIndex >= this.inputHistoryList.length) {
return true;
}
if (this.inputHistoryIndex < this.inputHistoryList.length - 1) {
this.inputHistoryIndex++;
this.inputValue = this.inputHistoryList[this.inputHistoryIndex] || '';
} else {
this.inputHistoryIndex = this.inputHistoryList.length;
this.inputValue = this.inputHistoryCurrent || '';
}
this.$nextTick(() => {
const ta = this.$refs.inputRef?.$el?.querySelector('textarea');
if (ta) {
const len = (this.inputValue || '').length;
ta.setSelectionRange(len, len);
}
});
return false;
}
return true;
},
// ==================== ====================
/**