mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-22 01:28:12 +00:00
feat(ai-assistant): 支持上下键切换历史输入
- 按 ↑ 键切换到上一条历史输入(光标在第一行时生效) - 按 ↓ 键切换到下一条历史输入(光标在最后一行时生效) - 历史记录使用 IndexedDB 持久化存储,最多保存 50 条 - 重复输入会移动到末尾而非重复添加 - 弹窗关闭时自动重置导航状态
This commit is contained in:
parent
c65f0276bd
commit
59ad79fa58
@ -254,6 +254,13 @@ export default {
|
|||||||
// 编辑历史问题
|
// 编辑历史问题
|
||||||
editingIndex: -1, // 正在编辑的响应索引,-1 表示不在编辑模式
|
editingIndex: -1, // 正在编辑的响应索引,-1 表示不在编辑模式
|
||||||
editingValue: '', // 编辑中的文本内容
|
editingValue: '', // 编辑中的文本内容
|
||||||
|
|
||||||
|
// 输入历史(上下键切换)
|
||||||
|
inputHistoryList: [], // 历史输入列表
|
||||||
|
inputHistoryIndex: 0, // 当前历史索引
|
||||||
|
inputHistoryCurrent: '', // 切换前的当前输入
|
||||||
|
inputHistoryCacheKey: 'aiAssistant.inputHistory',
|
||||||
|
inputHistoryLimit: 50,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -265,6 +272,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
emitter.on('openAIAssistant', this.onOpenAIAssistant);
|
emitter.on('openAIAssistant', this.onOpenAIAssistant);
|
||||||
this.loadCachedModel();
|
this.loadCachedModel();
|
||||||
|
this.loadInputHistory();
|
||||||
this.mountFloatButton();
|
this.mountFloatButton();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -560,16 +568,32 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入框键盘事件:回车发送,Shift+回车换行
|
* 输入框键盘事件:回车发送,Shift+回车换行,上下键切换历史
|
||||||
* 注意:输入法组合输入时(如中文候选字)不发送
|
* 注意:输入法组合输入时(如中文候选字)不发送
|
||||||
*/
|
*/
|
||||||
onInputKeydown(e) {
|
onInputKeydown(e) {
|
||||||
if (!e.shiftKey && !this.isComposing) {
|
if (this.isComposing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!e.shiftKey) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.onSubmit();
|
this.onSubmit();
|
||||||
} else if (e.key === 'Escape' && this.displayMode === 'chat') {
|
return;
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape' && this.displayMode === 'chat') {
|
||||||
this.showModal = false;
|
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,
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.persistInputHistory(prompt);
|
||||||
this.startStream(streamKey, responseEntry);
|
this.startStream(streamKey, responseEntry);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1047,6 +1072,7 @@ export default {
|
|||||||
this.pendingAutoSubmit = false;
|
this.pendingAutoSubmit = false;
|
||||||
this.clearAutoSubmitTimer();
|
this.clearAutoSubmitTimer();
|
||||||
this.clearActiveSSEClients();
|
this.clearActiveSSEClients();
|
||||||
|
this.resetInputHistoryNavigation();
|
||||||
this.showModal = false;
|
this.showModal = false;
|
||||||
this.responses = [];
|
this.responses = [];
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -1338,6 +1364,132 @@ export default {
|
|||||||
return time.format('YYYY-MM-DD HH:mm');
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 编辑历史问题 ====================
|
// ==================== 编辑历史问题 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user