diff --git a/resources/assets/js/components/AIAssistant/index.vue b/resources/assets/js/components/AIAssistant/index.vue
index b65113c2f..d4d2b0fd5 100644
--- a/resources/assets/js/components/AIAssistant/index.vue
+++ b/resources/assets/js/components/AIAssistant/index.vue
@@ -77,7 +77,32 @@
{{ response.modelLabel || response.model }}
- {{ response.prompt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ response.prompt }}
+
+
+
+
+
0) {
return;
}
- const rawValue = this.inputValue || '';
+ const prompt = (this.inputValue || '').trim();
+ if (!prompt) {
+ return;
+ }
+ const success = await this._doSendQuestion(prompt);
+ if (success) {
+ this.inputValue = '';
+ }
+ },
+
+ /**
+ * 执行发送问题的核心逻辑
+ * @param {string} prompt - 要发送的问题
+ * @returns {Promise} - 是否发送成功
+ * @private
+ */
+ async _doSendQuestion(prompt) {
const modelOption = this.selectedModelOption;
if (!modelOption) {
$A.messageWarning('请选择模型');
- return;
+ return false;
}
this.loadIng++;
let responseEntry = null;
try {
- const baseContext = this.collectBaseContext(rawValue);
+ const baseContext = this.collectBaseContext(prompt);
const context = await this.buildPayloadData(baseContext);
responseEntry = this.createResponseEntry({
modelOption,
- prompt: rawValue,
+ prompt,
});
this.scrollResponsesToBottom();
@@ -577,14 +622,15 @@ export default {
context,
});
- this.inputValue = '';
this.startStream(streamKey, responseEntry);
+ return true;
} catch (error) {
const msg = error?.msg || '发送失败';
if (responseEntry) {
this.markResponseError(responseEntry, msg);
}
$A.modalError(msg);
+ return false;
} finally {
this.loadIng--;
}
@@ -1291,6 +1337,75 @@ export default {
}
return time.format('YYYY-MM-DD HH:mm');
},
+
+ // ==================== 编辑历史问题 ====================
+
+ /**
+ * 开始编辑历史问题
+ */
+ startEditQuestion(index) {
+ if (index < 0 || index >= this.responses.length) {
+ return;
+ }
+ if (this.loadIng > 0) {
+ return;
+ }
+ this.editingIndex = index;
+ this.editingValue = this.responses[index].prompt || '';
+ this.$nextTick(() => {
+ // ref 在 v-for 中会变成数组
+ const inputRef = this.$refs.editInputRef;
+ const input = Array.isArray(inputRef) ? inputRef[0] : inputRef;
+ if (input && typeof input.focus === 'function') {
+ input.focus();
+ }
+ });
+ },
+
+ /**
+ * 取消编辑
+ */
+ cancelEditQuestion() {
+ this.editingIndex = -1;
+ this.editingValue = '';
+ },
+
+ /**
+ * 编辑器键盘事件
+ */
+ onEditKeydown(e) {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ this.cancelEditQuestion();
+ } else if (e.key === 'Enter' && !e.shiftKey && !this.isComposing) {
+ e.preventDefault();
+ this.submitEditedQuestion();
+ }
+ },
+
+ /**
+ * 提交编辑后的问题
+ */
+ async submitEditedQuestion() {
+ if (this.editingIndex < 0 || this.loadIng > 0) {
+ return;
+ }
+ const newPrompt = (this.editingValue || '').trim();
+ if (!newPrompt) {
+ $A.messageWarning('请输入问题');
+ return;
+ }
+
+ // 删除从编辑位置开始的所有响应
+ this.responses.splice(this.editingIndex);
+
+ // 重置编辑状态
+ this.editingIndex = -1;
+ this.editingValue = '';
+
+ // 发送新问题
+ await this._doSendQuestion(newPrompt);
+ },
},
}
@@ -1427,15 +1542,97 @@ export default {
padding: 2px 8px;
}
+ .ai-assistant-output-question-wrap {
+ margin-top: 8px;
+ }
+
.ai-assistant-output-question {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
+ display: flex;
+ align-items: flex-start;
+ gap: 4px;
font-size: 12px;
color: #666;
line-height: 1.4;
- margin-top: 8px;
+
+ .ai-assistant-output-question-text {
+ flex: 1;
+ min-width: 0;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
+ .ai-assistant-output-question-edit {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ color: #777;
+ border-radius: 4px;
+ margin-top: -2px;
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.2s, color 0.2s, background-color 0.2s;
+
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+
+ &:hover {
+ color: #444;
+ background-color: rgba(0, 0, 0, 0.06);
+ }
+ }
+
+ &:hover {
+ .ai-assistant-output-question-edit {
+ opacity: 1;
+ }
+ }
+ }
+
+ .ai-assistant-question-editor {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 8px;
+ background: #fff;
+ border: 1px solid #e8e8e8;
+ border-radius: 13px;
+
+ .ivu-input {
+ color: #333;
+ background-color: transparent;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
+ padding: 0 2px;
+ resize: none;
+ font-size: 12px;
+
+ &:hover,
+ &:focus {
+ border-color: transparent;
+ box-shadow: none;
+ }
+ }
+
+ .ai-assistant-question-editor-btns {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+
+ .ivu-btn {
+ height: 26px;
+ font-size: 12px;
+ padding: 0 9px;
+ border-radius: 13px;
+ }
+ }
}
.ai-assistant-output-placeholder {