feat(ai-assistant): AI 回复增加复制/时间/反馈取消,历史删除二次确认

- 反馈行新增复制按钮(去推理段落)与回复时间(规则参考 formatMessageTime)
- 点赞/点踩支持再点取消;修复 feedbackLoading 持久化导致重载后无法再点
- 历史删除改为二次确认(垃圾桶→红勾),离开/3秒/下拉关闭自动复位,触摸端常显
- 回复完成后贴底用户自动滚动到底部,露出反馈按钮
- 后端 feedback__save 支持空 feedback 取消(删除记录),同步 ai-kb

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-06-16 22:58:50 +00:00
parent 97bd58312e
commit b0a4fe6646
3 changed files with 203 additions and 34 deletions

View File

@ -244,7 +244,7 @@ class AssistantController extends AbstractController
/** /**
* @api {post} api/assistant/feedback/save 保存回复反馈 * @api {post} api/assistant/feedback/save 保存回复反馈
* *
* @apiDescription 需要token身份。保存用户对一条 AI 回复的 👍/👎 反馈,同一条回复可改票(覆盖更新) * @apiDescription 需要token身份。保存用户对一条 AI 回复的 👍/👎 反馈,同一条回复可改票(覆盖更新);传空 feedback 表示取消反馈(删除记录)
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup assistant * @apiGroup assistant
* @apiName feedback__save * @apiName feedback__save
@ -252,7 +252,7 @@ class AssistantController extends AbstractController
* @apiParam {String} session_key 场景分类key * @apiParam {String} session_key 场景分类key
* @apiParam {String} session_id 前端会话ID * @apiParam {String} session_id 前端会话ID
* @apiParam {Number} local_id 回复条目localId * @apiParam {Number} local_id 回复条目localId
* @apiParam {String} feedback like|dislike * @apiParam {String} feedback like|dislike,空字符串表示取消反馈
* @apiParam {String} [prompt] 用户问题 * @apiParam {String} [prompt] 用户问题
* @apiParam {String} [answer] 回复摘录 * @apiParam {String} [answer] 回复摘录
* @apiParam {Array} [source_ids] 回复引用的kb source id列表 * @apiParam {Array} [source_ids] 回复引用的kb source id列表
@ -279,7 +279,7 @@ class AssistantController extends AbstractController
if (empty($sessionId) || $localId <= 0) { if (empty($sessionId) || $localId <= 0) {
return Base::retError('参数错误'); return Base::retError('参数错误');
} }
if (!in_array($feedback, ['like', 'dislike'])) { if (!in_array($feedback, ['', 'like', 'dislike'])) {
return Base::retError('反馈类型错误'); return Base::retError('反馈类型错误');
} }
if (!is_array($sourceIds)) { if (!is_array($sourceIds)) {
@ -292,6 +292,14 @@ class AssistantController extends AbstractController
->where('local_id', $localId) ->where('local_id', $localId)
->first(); ->first();
// 空反馈表示取消:删除已有记录
if ($feedback === '') {
$exist?->delete();
return Base::retSuccess('success', [
'feedback' => '',
]);
}
$row = AiAssistantFeedback::createInstance([ $row = AiAssistantFeedback::createInstance([
'userid' => $user->userid, 'userid' => $user->userid,
'session_key' => $sessionKey, 'session_key' => $sessionKey,

View File

@ -11,6 +11,8 @@ aliases:
- AI 回答不好怎么反馈 - AI 回答不好怎么反馈
- 反馈 AI 回答 - 反馈 AI 回答
- 有帮助 没帮助 - 有帮助 没帮助
- 复制 AI 回答
- 取消反馈
related_tools: [] related_tools: []
related_pages: [] related_pages: []
prerequisites: prerequisites:
@ -18,18 +20,20 @@ prerequisites:
negative: negative:
- 反馈只针对 AI 助手浮窗里的回复,聊天对话里 @AI 机器人的消息暂不支持 - 反馈只针对 AI 助手浮窗里的回复,聊天对话里 @AI 机器人的消息暂不支持
- 点踩不会让 AI 立即重新回答,需要自己追问或重新提问 - 点踩不会让 AI 立即重新回答,需要自己追问或重新提问
last_verified: v1.7.90 last_verified: v1.7.91
--- ---
# 给 AI 回答点赞或点踩 # 给 AI 回答点赞或点踩
## 这是什么 ## 这是什么
AI 助手浮窗中,每条 AI 回复完成后下方会出现「有帮助 / 没帮助」两个图标按钮(👍/👎)。点击即提交反馈,用于帮助官方改进 AI 回答质量和帮助文档内容。 AI 助手浮窗中,每条 AI 回复完成后下方右侧会出现「复制 / 有帮助 / 没帮助」三个图标按钮(📋/👍/👎)。复制用于把回复内容复制到剪贴板,👍/👎 用于提交反馈,帮助官方改进 AI 回答质量和帮助文档内容。
## 怎么操作 ## 怎么操作
1. 在 AI 助手浮窗中提问,等待回复完成(流式输出结束后按钮才出现) 1. 在 AI 助手浮窗中提问,等待回复完成(流式输出结束后按钮才出现)
2. 回复下方右侧点击 👍(有帮助)或 👎(没帮助) 2. 回复下方右侧点击 📋(复制)可复制该条回复正文到剪贴板(不含推理过程)
3. 按钮高亮表示已提交;再点另一个按钮可以改票,同一条回复只记最新一次 3. 点击 👍(有帮助)或 👎(没帮助)提交反馈
4. 按钮高亮表示已提交;再点另一个按钮可以改票,同一条回复只记最新一次
5. 再次点击当前已高亮的按钮可取消反馈
## 反馈会被怎么用 ## 反馈会被怎么用
- 反馈与该回复引用的帮助文档关联,被频繁点踩的文档会被优先修订 - 反馈与该回复引用的帮助文档关联,被频繁点踩的文档会被优先修订
@ -37,7 +41,6 @@ AI 助手浮窗中,每条 AI 回复完成后下方会出现「有帮助 / 没
## 不支持 ## 不支持
- 不支持填写文字原因,只有 👍/👎 两档 - 不支持填写文字原因,只有 👍/👎 两档
- 不支持取消反馈(可改票,不可清除)
## 相关 ## 相关
- AI 查帮助文档:[[ai-assistant.search-help-docs.howto]] - AI 查帮助文档:[[ai-assistant.search-help-docs.howto]]

View File

@ -18,7 +18,8 @@
trigger="click" trigger="click"
placement="bottom-end" placement="bottom-end"
:transfer="true" :transfer="true"
:z-index="topZIndex + 1"> :z-index="topZIndex + 1"
@on-visible-change="onHistoryVisibleChange">
<div class="ai-assistant-header-btn" :title="$L('历史会话')"> <div class="ai-assistant-header-btn" :title="$L('历史会话')">
<i class="taskfont">&#xe6e8;</i> <i class="taskfont">&#xe6e8;</i>
</div> </div>
@ -28,10 +29,21 @@
:key="session.id" :key="session.id"
:class="{'active': session.id === currentSessionId}" :class="{'active': session.id === currentSessionId}"
@click.native="loadSession(session.id)"> @click.native="loadSession(session.id)">
<div class="history-item"> <div class="history-item" @mouseleave="resetDeleteConfirm">
<div class="history-item-content"> <div class="history-item-content">
<div class="history-item-title">{{ session.title }}</div> <div class="history-item-title">{{ session.title }}</div>
<div class="history-item-delete" @click.stop="deleteSession(session.id)"> <div
v-if="confirmingDeleteId === session.id"
class="history-item-delete history-item-delete-confirm"
:title="$L('确认删除')"
@click.stop="deleteSession(session.id)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<div
v-else
class="history-item-delete"
:title="$L('删除')"
@click.stop="askDeleteSession(session.id)">
<i class="taskfont">&#xe6e5;</i> <i class="taskfont">&#xe6e5;</i>
</div> </div>
</div> </div>
@ -142,18 +154,25 @@
<div <div
v-if="response.rawOutput && response.status === 'completed'" v-if="response.rawOutput && response.status === 'completed'"
class="ai-assistant-output-feedback"> class="ai-assistant-output-feedback">
<span
class="ai-assistant-feedback-btn"
:title="$L('复制')"
@click="copyResponse(response)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.0"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
</span>
<span <span
:class="['ai-assistant-feedback-btn', {active: response.feedback === 'like'}]" :class="['ai-assistant-feedback-btn', {active: response.feedback === 'like'}]"
:title="$L('有帮助')" :title="$L('有帮助')"
@click="submitFeedback(response, 'like')"> @click="submitFeedback(response, 'like')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path stroke-linecap="round" stroke-linejoin="round" d="M6.633 10.25c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 0 1 2.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 0 0 .322-1.672V3a.75.75 0 0 1 .75-.75 2.25 2.25 0 0 1 2.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 0 1-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 0 0-1.423-.23H5.904m1.729-7.5a8.97 8.97 0 0 0-.621 4.72c.063.504.123 1.012.182 1.52.04.35.05.703.05 1.06v.27c0 .415-.336.75-.75.75h-2.25a.75.75 0 0 1-.75-.75v-7.5a.75.75 0 0 1 .75-.75h2.25c.414 0 .75.335.75.75v.198Z"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.0"><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/><path d="M7 10v12"/></svg>
</span> </span>
<span <span
:class="['ai-assistant-feedback-btn ai-assistant-feedback-btn-down', {active: response.feedback === 'dislike'}]" :class="['ai-assistant-feedback-btn ai-assistant-feedback-btn-down', {active: response.feedback === 'dislike'}]"
:title="$L('没帮助')" :title="$L('没帮助')"
@click="submitFeedback(response, 'dislike')"> @click="submitFeedback(response, 'dislike')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path stroke-linecap="round" stroke-linejoin="round" d="M6.633 10.25c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 0 1 2.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 0 0 .322-1.672V3a.75.75 0 0 1 .75-.75 2.25 2.25 0 0 1 2.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 0 1-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 0 0-1.423-.23H5.904m1.729-7.5a8.97 8.97 0 0 0-.621 4.72c.063.504.123 1.012.182 1.52.04.35.05.703.05 1.06v.27c0 .415-.336.75-.75.75h-2.25a.75.75 0 0 1-.75-.75v-7.5a.75.75 0 0 1 .75-.75h2.25c.414 0 .75.335.75.75v.198Z"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.0"><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/><path d="M17 14V2"/></svg>
</span> </span>
<span v-if="response.createdAt" class="ai-assistant-output-time" :title="$A.dayjs(response.createdAt).format('YYYY-MM-DD HH:mm:ss')">{{ formatResponseTime(response.createdAt) }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -273,6 +292,8 @@ export default {
loadIng: 0, loadIng: 0,
pendingAutoSubmit: false, pendingAutoSubmit: false,
autoSubmitTimer: null, autoSubmitTimer: null,
confirmingDeleteId: null,
confirmingDeleteTimer: null,
modalTitle: null, modalTitle: null,
applyButtonText: null, applyButtonText: null,
submitButtonText: null, submitButtonText: null,
@ -368,6 +389,7 @@ export default {
emitter.off('openAIAssistant', this.onOpenAIAssistant); emitter.off('openAIAssistant', this.onOpenAIAssistant);
this.clearActiveSSEClients(); this.clearActiveSSEClients();
this.clearAutoSubmitTimer(); this.clearAutoSubmitTimer();
this.resetDeleteConfirm();
this.unmountFloatButton(); this.unmountFloatButton();
this.refreshWelcomePromptsDebounced?.cancel(); this.refreshWelcomePromptsDebounced?.cancel();
this.stopZIndexTimer(); this.stopZIndexTimer();
@ -1105,6 +1127,8 @@ export default {
handleStreamDone(owner, sse, event) { handleStreamDone(owner, sse, event) {
const donePayload = this.parseStreamPayload(event); const donePayload = this.parseStreamPayload(event);
const target = this.locateStreamTarget(owner); const target = this.locateStreamTarget(owner);
// 完成前先判断是否仍贴底(此时反馈行尚未渲染)
const stickToBottom = target.isCurrent && this.shouldStickToBottom();
if (target.entry) { if (target.entry) {
if (donePayload && donePayload.error) { if (donePayload && donePayload.error) {
this.markResponseError(target.entry, donePayload.error); this.markResponseError(target.entry, donePayload.error);
@ -1114,6 +1138,10 @@ export default {
} }
this.releaseSSEClient(sse); this.releaseSSEClient(sse);
this.persistStreamTarget(target); this.persistStreamTarget(target);
// 完成后反馈/时间等按钮新增了高度,贴底用户需要再滚到底部
if (stickToBottom) {
this.scrollResponsesToBottom();
}
}, },
/** /**
@ -1325,6 +1353,7 @@ export default {
error: '', error: '',
applyLoading: false, applyLoading: false,
feedback: '', feedback: '',
createdAt: Date.now(),
}; };
this.responses.push(entry); this.responses.push(entry);
if (this.responses.length > this.maxResponses) { if (this.responses.length > this.maxResponses) {
@ -1427,14 +1456,27 @@ export default {
}, },
/** /**
* 提交 👍/👎 反馈(可改票:点另一个值覆盖更新) * 复制回复内容(去除推理段落)
*/ */
async submitFeedback(response, value) { copyResponse(response) {
if (!response || response.feedbackLoading || response.feedback === value) { const text = this.removeReasoningSections(response?.displayOutput || response?.rawOutput) || '';
if (!text) {
return; return;
} }
this.copyText(text);
},
/**
* 提交 👍/👎 反馈(可改票:点另一个值覆盖更新;再点当前已选项则取消)
*/
async submitFeedback(response, value) {
if (!response || response.feedbackLoading) {
return;
}
// 点击当前已选中的反馈表示取消
const next = response.feedback === value ? '' : value;
const prev = response.feedback; const prev = response.feedback;
this.$set(response, 'feedback', value); this.$set(response, 'feedback', next);
this.$set(response, 'feedbackLoading', true); this.$set(response, 'feedbackLoading', true);
try { try {
await this.$store.dispatch("call", { await this.$store.dispatch("call", {
@ -1444,19 +1486,20 @@ export default {
session_key: this.currentSessionKey, session_key: this.currentSessionKey,
session_id: this.currentSessionId || '', session_id: this.currentSessionId || '',
local_id: response.localId, local_id: response.localId,
feedback: value, feedback: next,
prompt: this.parsePromptContent(response.prompt).text.substring(0, 1000), prompt: this.parsePromptContent(response.prompt).text.substring(0, 1000),
answer: (this.removeReasoningSections(response.rawOutput) || '').substring(0, 2000), answer: (this.removeReasoningSections(response.rawOutput) || '').substring(0, 2000),
source_ids: this.extractSourceIds(response.rawOutput), source_ids: this.extractSourceIds(response.rawOutput),
model: response.model, model: response.model,
}, },
}); });
// 先复位 loading 再保存,避免把 feedbackLoading=true 持久化导致重载后无法再点
this.$set(response, 'feedbackLoading', false);
this.saveCurrentSession(); this.saveCurrentSession();
} catch (e) { } catch (e) {
this.$set(response, 'feedback', prev); this.$set(response, 'feedback', prev);
$A.messageError(e?.msg || '反馈失败,请重试');
} finally {
this.$set(response, 'feedbackLoading', false); this.$set(response, 'feedbackLoading', false);
$A.messageError(e?.msg || '反馈失败,请重试');
} }
}, },
@ -1554,6 +1597,8 @@ export default {
r.status = 'error'; r.status = 'error';
r.error = r.error || this.$L('会话中断'); r.error = r.error || this.$L('会话中断');
} }
// feedbackLoading 为瞬时态,恢复时强制复位,避免历史脏数据卡死反馈按钮
r.feedbackLoading = false;
}); });
} }
// 缓存服务端返回的图片URL映射 // 缓存服务端返回的图片URL映射
@ -1577,10 +1622,12 @@ export default {
*/ */
sanitizeResponsesForPersist(responses) { sanitizeResponsesForPersist(responses) {
return (responses || []).map(r => { return (responses || []).map(r => {
if (r.status === 'streaming' || r.status === 'waiting') { // feedbackLoading 为瞬时 UI 态,不持久化,避免重载后卡死反馈按钮
return {...r, status: 'error', error: r.error || this.$L('会话中断')}; const {feedbackLoading, ...rest} = r;
if (rest.status === 'streaming' || rest.status === 'waiting') {
return {...rest, status: 'error', error: rest.error || this.$L('会话中断')};
} }
return r; return rest;
}); });
}, },
@ -1803,7 +1850,35 @@ export default {
/** /**
* 删除指定会话 * 删除指定会话
*/ */
/**
* 第一次点击删除:进入「确认删除」态(不立即删除),3 秒后自动复位
*/
askDeleteSession(sessionId) {
this.confirmingDeleteId = sessionId;
clearTimeout(this.confirmingDeleteTimer);
this.confirmingDeleteTimer = setTimeout(() => {
this.resetDeleteConfirm();
}, 3000);
},
/**
* 复位「确认删除」态
*/
resetDeleteConfirm() {
this.confirmingDeleteId = null;
clearTimeout(this.confirmingDeleteTimer);
this.confirmingDeleteTimer = null;
},
/**
* 历史下拉显隐变化时复位「确认删除」态
*/
onHistoryVisibleChange() {
this.resetDeleteConfirm();
},
deleteSession(sessionId) { deleteSession(sessionId) {
this.resetDeleteConfirm();
const index = this.sessionStore.findIndex(s => s.id === sessionId); const index = this.sessionStore.findIndex(s => s.id === sessionId);
if (index > -1) { if (index > -1) {
const session = this.sessionStore[index]; const session = this.sessionStore[index];
@ -1850,6 +1925,40 @@ export default {
/** /**
* 格式化会话时间显示 * 格式化会话时间显示
*/ */
/**
* 格式化单条回复时间(规则参考 happy-next formatMessageTime)
* - 今天:HH:mm
* - 本周内(周一为起点):周几 HH:mm
* - 今年内:MM-DD HH:mm
* - 更早:YYYY-MM-DD
*/
formatResponseTime(timestamp) {
if (!timestamp) {
return '';
}
const now = $A.daytz();
const time = $A.dayjs(timestamp);
const hm = time.format('HH:mm');
// 今天
if (now.format('YYYY-MM-DD') === time.format('YYYY-MM-DD')) {
return hm;
}
// 本周内(周一 00:00 起点,且不晚于当前)
const weekStart = now.subtract((now.day() + 6) % 7, 'day').startOf('day');
if (time.valueOf() >= weekStart.valueOf() && time.valueOf() <= now.valueOf()) {
const map = {zh: 'zh-CN', 'zh-CHT': 'zh-TW'};
const lang = getLanguage();
const locale = map[lang] || lang || 'en';
const weekday = new Intl.DateTimeFormat(locale, {weekday: 'short'}).format(time.toDate());
return `${weekday} ${hm}`;
}
// 今年内
if (now.year() === time.year()) {
return time.format('MM-DD HH:mm');
}
return time.format('YYYY-MM-DD');
},
formatSessionTime(timestamp) { formatSessionTime(timestamp) {
const now = $A.daytz(); const now = $A.daytz();
const time = $A.dayjs(timestamp); const time = $A.dayjs(timestamp);
@ -2698,19 +2807,21 @@ export default {
} }
.ai-assistant-output-placeholder { .ai-assistant-output-placeholder {
height: 34px;
line-height: 34px;
margin-top: 12px; margin-top: 12px;
font-size: 13px; font-size: 13px;
color: #999; color: #999;
padding: 8px; padding: 0 10px;
border-radius: 6px; border-radius: 6px;
background: rgba(0, 0, 0, 0.02); background: rgba(0, 0, 0, 0.02);
} }
.ai-assistant-output-feedback { .ai-assistant-output-feedback {
margin-top: 6px; margin-top: 8px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 4px; gap: 3px;
.ai-assistant-feedback-btn { .ai-assistant-feedback-btn {
display: inline-flex; display: inline-flex;
@ -2724,8 +2835,8 @@ export default {
transition: all 0.2s; transition: all 0.2s;
svg { svg {
width: 15px; width: 14px;
height: 15px; height: 14px;
} }
&:hover { &:hover {
@ -2734,26 +2845,32 @@ export default {
} }
&.active { &.active {
color: var(--primary-color, #1677ff); color: var(--primary-color, #8bcf70);
background: rgba(22, 119, 255, 0.08); background: rgba(139, 207, 112, 0.08);
} }
&.ai-assistant-feedback-btn-down { &.ai-assistant-feedback-btn-down {
svg {
transform: rotate(180deg);
}
&.active { &.active {
color: #f56c6c; color: #f56c6c;
background: rgba(245, 108, 108, 0.08); background: rgba(245, 108, 108, 0.08);
} }
} }
} }
.ai-assistant-output-time {
display: inline-flex;
align-items: center;
margin-left: 4px;
font-size: 12px;
color: #bbb;
white-space: nowrap;
}
} }
.ai-assistant-output-markdown { .ai-assistant-output-markdown {
margin-top: 12px; margin-top: 12px;
font-size: 13px; font-size: 13px;
min-height: 34px;
.apply-reasoning { .apply-reasoning {
margin: 0 0 12px 0; margin: 0 0 12px 0;
@ -2966,6 +3083,22 @@ export default {
font-size: 12px; font-size: 12px;
color: #909399; color: #909399;
} }
&.history-item-delete-confirm {
display: flex;
color: #fff;
background-color: #f56c6c;
opacity: 1;
&:hover {
background-color: #e15a5a;
}
> svg {
width: 13px;
height: 13px;
}
}
} }
} }
@ -2981,6 +3114,16 @@ export default {
} }
} }
} }
// 触摸设备无 hover,删除按钮常显
@media (pointer: coarse) {
.history-item-content {
.history-item-delete {
display: flex;
opacity: 0.6;
}
}
}
} }
.history-clear { .history-clear {
@ -3317,6 +3460,21 @@ export default {
} }
} }
body.window-portrait {
.ai-assistant-content {
.ai-assistant-output-feedback {
gap: 6px;
.ai-assistant-feedback-btn {
svg {
width: 15px;
height: 15px;
}
}
}
}
}
body.dark-mode-reverse { body.dark-mode-reverse {
.ai-assistant-content { .ai-assistant-content {
.ai-assistant-welcome, .ai-assistant-welcome,