|
|
|
|
|
<template>
|
|
|
|
|
|
<aside :class="['training-ai-sidebar', { 'is-collapsed': sidebarCollapsed }]">
|
|
|
|
|
|
<!-- 收起态:右下角圆形悬浮球 -->
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="sidebarCollapsed"
|
|
|
|
|
|
class="ai-fab"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="展开 AI 助学与助评"
|
|
|
|
|
|
title="AI 助学助手"
|
|
|
|
|
|
@click="sidebarCollapsed = false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon class="ai-fab__icon"><MagicStick /></el-icon>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 展开态 -->
|
|
|
|
|
|
<div v-else class="ai-sidebar-body">
|
|
|
|
|
|
<header class="ai-head">
|
|
|
|
|
|
<span class="ai-head__brand">
|
|
|
|
|
|
<el-icon><MagicStick /></el-icon>
|
|
|
|
|
|
AI 学习助手
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="ai-head__close"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="收起 AI 助学与助评"
|
|
|
|
|
|
title="收起"
|
|
|
|
|
|
@click="sidebarCollapsed = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span aria-hidden="true">›</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ai-segmented" role="tablist">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="['ai-segmented__item', { on: activePanel === 'study' }]"
|
|
|
|
|
|
:aria-selected="String(activePanel === 'study')"
|
|
|
|
|
|
@click="activePanel = 'study'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><ChatDotRound /></el-icon>AI 助学
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="['ai-segmented__item', { on: activePanel === 'review' }]"
|
|
|
|
|
|
:aria-selected="String(activePanel === 'review')"
|
|
|
|
|
|
@click="activePanel = 'review'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><DataAnalysis /></el-icon>AI 助评
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ========== AI 助学 ========== -->
|
|
|
|
|
|
<div v-show="activePanel === 'study'" class="ai-pane">
|
|
|
|
|
|
<div ref="chatStreamRef" class="chat-stream" aria-live="polite">
|
|
|
|
|
|
<div v-if="!assistMessages.length" class="chat-line chat-line--ai">
|
|
|
|
|
|
<span class="chat-avatar">AI</span>
|
|
|
|
|
|
<p class="chat-text chat-text--ai">{{ displayAssistMessage }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template v-for="message in assistMessages" :key="message.id">
|
|
|
|
|
|
<div v-if="message.role === 'student'" class="chat-line chat-line--user">
|
|
|
|
|
|
<p class="chat-text chat-text--user">{{ message.content }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="chat-line chat-line--ai">
|
|
|
|
|
|
<span class="chat-avatar">AI</span>
|
|
|
|
|
|
<p class="chat-text chat-text--ai">{{ message.content }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<div v-if="assistLoading" class="chat-line chat-line--ai">
|
|
|
|
|
|
<span class="chat-avatar">AI</span>
|
|
|
|
|
|
<p class="chat-thinking">
|
|
|
|
|
|
AI 正在思考
|
|
|
|
|
|
<span class="thinking-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button v-if="referenceText" type="button" class="reference-card" @click="useReferencePrompt">
|
|
|
|
|
|
<span class="reference-card__title">
|
|
|
|
|
|
<el-icon><Reading /></el-icon>{{ referenceTitle }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="reference-card__text" v-html="referenceText"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="composer">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
ref="assistInputRef"
|
|
|
|
|
|
v-model="assistQuestion"
|
|
|
|
|
|
placeholder="输入问题,Enter 发送 / Shift+Enter 换行"
|
|
|
|
|
|
rows="1"
|
|
|
|
|
|
@input="autoGrowAssist"
|
|
|
|
|
|
@keydown.enter.exact.prevent="requestAssist"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="composer__send"
|
|
|
|
|
|
aria-label="发送助学问题"
|
|
|
|
|
|
:disabled="assistLoading"
|
|
|
|
|
|
@click="requestAssist"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><Top /></el-icon>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ========== AI 助评 ========== -->
|
|
|
|
|
|
<div v-show="activePanel === 'review'" class="ai-pane">
|
|
|
|
|
|
<div v-if="displayScoreVisible" class="score-row">
|
|
|
|
|
|
<span class="score-row__label">综合评分</span>
|
|
|
|
|
|
<span class="score-row__value">
|
|
|
|
|
|
<strong>{{ displayFinalScore }}</strong><em>/ 100</em>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="review-scroll">
|
|
|
|
|
|
<div v-if="hasStructuredAssessment" class="review-result">
|
|
|
|
|
|
<p v-if="reports.assessmentSummary" class="review-summary">{{ reports.assessmentSummary }}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="displayScoreCriteria.length" class="criteria" aria-label="评分明细">
|
|
|
|
|
|
<div v-for="item in displayScoreCriteria" :key="`${item.name}-${item.score}`" class="criterion">
|
|
|
|
|
|
<div class="criterion__top">
|
|
|
|
|
|
<span class="criterion__name">{{ item.name }}</span>
|
|
|
|
|
|
<span v-if="item.score !== ''" class="criterion__score">
|
|
|
|
|
|
{{ item.score }}<em v-if="item.maxScore !== ''"> / {{ item.maxScore }}</em>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p v-if="item.description" class="criterion__desc">{{ item.description }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="reports.improvements.length" class="improvements">
|
|
|
|
|
|
<span class="improvements__title">优先改进</span>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li v-for="item in reports.improvements" :key="item">{{ item }}</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else class="review-empty">
|
|
|
|
|
|
<el-icon><DataLine /></el-icon>
|
|
|
|
|
|
<p>{{ displayEvalMessage }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="currentTaskKey && !hasSavedAnswer" class="review-hint">请先至少填写并保存一项实训内容,再请求 AI 助评。</p>
|
|
|
|
|
|
<p v-else-if="currentTaskKey" class="review-hint">修改并保存答案后可重新助评;答案未变时保留上次结果。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="review-primary"
|
|
|
|
|
|
:disabled="!currentTaskKey || !hasSavedAnswer || assessmentLoading"
|
|
|
|
|
|
@click="requestEvaluation"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ assessmentLoading ? "AI 评分中…" : reports.assessment.status === "SUCCEEDED" ? "重新助评" : "AI 智能评分" }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ChatDotRound, DataAnalysis, DataLine, MagicStick, Reading, Top } from "@element-plus/icons-vue";
|
|
|
|
|
|
import { getAiTrainingEvaluation, requestAiTrainingAssessment, requestAiTrainingHelp } from "@/api/aiTrainingEvaluation";
|
|
|
|
|
|
import { normalizeEvaluationReports } from "./trainingAiEvaluationReport";
|
|
|
|
|
|
import { resolveTrainingTaskKey } from "@/views/training/taskKeyMap";
|
|
|
|
|
|
import { buildReferencePrompt } from "./studentTrainingAiPanel";
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
studyTip: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "当前任务建议:先明确任务要求,再结合真实业务场景逐步补全实训内容。",
|
|
|
|
|
|
},
|
|
|
|
|
|
referenceTitle: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "填写提醒",
|
|
|
|
|
|
},
|
|
|
|
|
|
referenceText: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
assistText: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "建议已生成:请围绕任务目标补充关键数据、分析过程和结论依据。",
|
|
|
|
|
|
},
|
|
|
|
|
|
evalMessage: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: "完成实训内容后,可以请求AI助评获取智能评分和改进建议。",
|
|
|
|
|
|
},
|
|
|
|
|
|
finalScore: {
|
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
|
default: "--",
|
|
|
|
|
|
},
|
|
|
|
|
|
scoreVisible: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
progressItems: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => [],
|
|
|
|
|
|
},
|
|
|
|
|
|
taskKey: { type: String, default: "" },
|
|
|
|
|
|
hasSavedAnswer: { type: Boolean, default: true },
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(["request-assist", "request-evaluation"]);
|
|
|
|
|
|
|
|
|
|
|
|
const assistQuestion = ref("");
|
|
|
|
|
|
const reviewQuestion = ref("");
|
|
|
|
|
|
const assistMessage = ref("");
|
|
|
|
|
|
const internalEvalMessage = ref("");
|
|
|
|
|
|
const internalScore = ref("--");
|
|
|
|
|
|
const internalScoreVisible = ref(false);
|
|
|
|
|
|
const assessmentLoading = ref(false);
|
|
|
|
|
|
const assistLoading = ref(false);
|
|
|
|
|
|
const evaluation = ref({});
|
|
|
|
|
|
const assistMessages = ref([]);
|
|
|
|
|
|
const sidebarCollapsed = ref(true);
|
|
|
|
|
|
const activePanel = ref("study");
|
|
|
|
|
|
const chatStreamRef = ref(null);
|
|
|
|
|
|
const assistInputRef = ref(null);
|
|
|
|
|
|
/** 输入框最大高度(约 6 行):到此停止增高,内容在框内滚动(DeepSeek 式) */
|
|
|
|
|
|
const ASSIST_INPUT_MAX_HEIGHT = 168;
|
|
|
|
|
|
function autoGrowAssist() {
|
|
|
|
|
|
const el = assistInputRef.value;
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
el.style.height = "auto";
|
|
|
|
|
|
el.style.height = `${Math.min(el.scrollHeight, ASSIST_INPUT_MAX_HEIGHT)}px`;
|
|
|
|
|
|
}
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const currentTaskKey = computed(() => resolveTrainingTaskKey(route, props.taskKey));
|
|
|
|
|
|
const reports = computed(() => normalizeEvaluationReports(evaluation.value));
|
|
|
|
|
|
const currentStepContext = computed(() => [props.studyTip, props.referenceTitle, plainText(props.referenceText)].filter(Boolean).join("\n"));
|
|
|
|
|
|
const displayScoreCriteria = computed(() => {
|
|
|
|
|
|
const criteria = reports.value.scoreCriteria;
|
|
|
|
|
|
const stepNames = props.progressItems.map((item) => String(item?.text || "").trim()).filter(Boolean);
|
|
|
|
|
|
if (!stepNames.length || stepNames.length !== criteria.length) return criteria;
|
|
|
|
|
|
return criteria.map((item, index) => ({ ...item, name: stepNames[index] }));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const displayAssistMessage = computed(() => assistMessage.value || props.studyTip);
|
|
|
|
|
|
const displayScoreVisible = computed(() => props.scoreVisible || internalScoreVisible.value || reports.value.score !== null);
|
|
|
|
|
|
const displayFinalScore = computed(() => (props.scoreVisible ? props.finalScore : reports.value.score ?? internalScore.value));
|
|
|
|
|
|
const hasStructuredAssessment = computed(() => Boolean(reports.value.assessmentSummary || displayScoreCriteria.value.length || reports.value.improvements.length));
|
|
|
|
|
|
const displayEvalMessage = computed(() => {
|
|
|
|
|
|
if (props.scoreVisible) return props.evalMessage;
|
|
|
|
|
|
return internalEvalMessage.value || props.evalMessage;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(loadEvaluation);
|
|
|
|
|
|
watch(currentTaskKey, (nextTaskKey, previousTaskKey) => {
|
|
|
|
|
|
if (nextTaskKey !== previousTaskKey) assistMessages.value = [];
|
|
|
|
|
|
loadEvaluation();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/* 新消息/思考态出现时,对话流自动滚到底部 */
|
|
|
|
|
|
watch([assistMessages, assistLoading], async () => {
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
if (chatStreamRef.value) chatStreamRef.value.scrollTop = chatStreamRef.value.scrollHeight;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/* 输入框按内容自适应增高:覆盖参考提示词填充、发送清空两种程序化变更 */
|
|
|
|
|
|
watch(assistQuestion, async () => {
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
autoGrowAssist();
|
|
|
|
|
|
});
|
|
|
|
|
|
/* 面板展开后 textarea 重新挂载,初始校准一次 */
|
|
|
|
|
|
watch(sidebarCollapsed, async (collapsed) => {
|
|
|
|
|
|
if (!collapsed) {
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
autoGrowAssist();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
async function loadEvaluation() {
|
|
|
|
|
|
if (!currentTaskKey.value) return;
|
|
|
|
|
|
try { evaluation.value = (await getAiTrainingEvaluation(currentTaskKey.value))?.data || {}; } catch (error) { evaluation.value = {}; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function useReferencePrompt() {
|
|
|
|
|
|
assistQuestion.value = buildReferencePrompt(props.referenceText);
|
|
|
|
|
|
activePanel.value = "study";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function requestAssist() {
|
|
|
|
|
|
emit("request-assist", assistQuestion.value);
|
|
|
|
|
|
const question = assistQuestion.value.trim();
|
|
|
|
|
|
if (!question) {
|
|
|
|
|
|
assistMessage.value = "请输入问题,或点击上方参考提示词后发送。";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (currentTaskKey.value && !props.hasSavedAnswer) {
|
|
|
|
|
|
assistMessage.value = "请先至少填写并保存一项实训内容,再请求 AI 助学。";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
assistMessages.value.push({ id: `student-${Date.now()}`, role: "student", content: question });
|
|
|
|
|
|
assistQuestion.value = "";
|
|
|
|
|
|
if (currentTaskKey.value) {
|
|
|
|
|
|
assistLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await requestAiTrainingHelp(currentTaskKey.value, question, currentStepContext.value);
|
|
|
|
|
|
await loadEvaluation();
|
|
|
|
|
|
assistMessage.value = reports.value.helpReport || props.assistText;
|
|
|
|
|
|
assistMessages.value.push({ id: `assistant-${Date.now()}`, role: "assistant", content: assistMessage.value });
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
assistMessages.value.push({ id: `assistant-${Date.now()}`, role: "assistant", content: "AI 助学暂时不可用,请稍后重试。" });
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
assistLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
assistMessage.value = `围绕“${question}”,建议先对照任务目标检查信息是否完整,再补充数据依据和分析结论。`;
|
|
|
|
|
|
assistMessages.value.push({ id: `assistant-${Date.now()}`, role: "assistant", content: assistMessage.value });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function requestEvaluation() {
|
|
|
|
|
|
emit("request-evaluation", reviewQuestion.value);
|
|
|
|
|
|
activePanel.value = "review";
|
|
|
|
|
|
if (!currentTaskKey.value || !props.hasSavedAnswer) {
|
|
|
|
|
|
internalEvalMessage.value = "请先至少填写并保存一项实训内容,再请求 AI 助评。";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
assessmentLoading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await requestAiTrainingAssessment(currentTaskKey.value);
|
|
|
|
|
|
await loadEvaluation();
|
|
|
|
|
|
internalScoreVisible.value = reports.value.score !== null;
|
|
|
|
|
|
internalScore.value = reports.value.score ?? "--";
|
|
|
|
|
|
internalEvalMessage.value = reports.value.assessmentSummary || "AI 评分已生成,请查看评分明细与改进建议。";
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
internalEvalMessage.value = "AI 助评暂时不可用,请稍后重试。";
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
assessmentLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function plainText(value) {
|
|
|
|
|
|
return String(value || "")
|
|
|
|
|
|
.replace(/<[^>]*>/g, " ")
|
|
|
|
|
|
.replace(/ /g, " ")
|
|
|
|
|
|
.replace(/\s+/g, " ")
|
|
|
|
|
|
.trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
/*
|
|
|
|
|
|
* 组件自带「墨黑极简」局部令牌(不依赖页面级 var(--student-*) 令牌),
|
|
|
|
|
|
* 保证在所有实训页(含未做墨黑换肤的页面)视觉一致。
|
|
|
|
|
|
* 宽度/定位不在这里设置——由全局 student-training.scss 的 shell grid 控制。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 字体规范(对齐大厂悬浮面板):
|
|
|
|
|
|
* - Claude 设计指南:三级字号(标题/正文/标注)+ 两种字重(常规/强调)
|
|
|
|
|
|
* - Fluent 2(Copilot 侧栏):Caption 12/16、Body 14/20、Title3 24/32
|
|
|
|
|
|
* - Material 3 / Ant Design 5:正文锚点 14px,标注 12px
|
|
|
|
|
|
* - 中文按 M3「中高语言」类别行高上调,正文 14/22(≈1.6)
|
|
|
|
|
|
*/
|
|
|
|
|
|
.training-ai-sidebar {
|
|
|
|
|
|
--ai-accent: #2563eb;
|
|
|
|
|
|
--ai-accent-deep: #1d4ed8;
|
|
|
|
|
|
--ai-gray: #6b7280;
|
|
|
|
|
|
--ai-gray-light: #9ca3af;
|
|
|
|
|
|
--ai-soft: #f3f4f6;
|
|
|
|
|
|
--ai-soft-2: #fafbfc;
|
|
|
|
|
|
--ai-border: #e5e7eb;
|
|
|
|
|
|
--ai-border-strong: #d1d5db;
|
|
|
|
|
|
--ai-text: #1f2937;
|
|
|
|
|
|
--ai-text-2: #4b5563;
|
|
|
|
|
|
--ai-success: #4a9d6e;
|
|
|
|
|
|
|
|
|
|
|
|
/* 字体栈:跟随系统,中文优先苹方/雅黑 */
|
|
|
|
|
|
--ai-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
|
|
|
|
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue",
|
|
|
|
|
|
Helvetica, Arial, sans-serif;
|
|
|
|
|
|
/* 三级字号 */
|
|
|
|
|
|
--ai-fs-caption: 12px; /* 标注:提示、元信息、空状态、分段、标签 */
|
|
|
|
|
|
--ai-fs-body: 14px; /* 正文:对话、输入器、列表、摘要、按钮 */
|
|
|
|
|
|
--ai-fs-display: 24px; /* 展示:综合评分(Fluent Title 3) */
|
|
|
|
|
|
/* 行高 */
|
|
|
|
|
|
--ai-lh-caption: 18px; /* 1.5 */
|
|
|
|
|
|
--ai-lh-body: 22px; /* ≈1.57,中文阅读 */
|
|
|
|
|
|
/* 两种字重 */
|
|
|
|
|
|
--ai-fw-regular: 400;
|
|
|
|
|
|
--ai-fw-emphasized: 600;
|
|
|
|
|
|
|
|
|
|
|
|
font-family: var(--ai-font-family);
|
|
|
|
|
|
/* Web 悬浮面板标准:开启字体抗锯齿(ChatGPT/Copilot 同款渲染) */
|
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
|
-moz-osx-font-smoothing: grayscale;
|
|
|
|
|
|
|
|
|
|
|
|
/* 真正悬浮:脱离文档流,不占用页面宽度与 grid 列 */
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
z-index: 1001;
|
|
|
|
|
|
top: 64px;
|
|
|
|
|
|
right: 16px;
|
|
|
|
|
|
bottom: 16px;
|
|
|
|
|
|
width: min(585px, calc(100vw - 32px));
|
|
|
|
|
|
/* 根透明:不铺任何背景色,白底仅限卡片本身 */
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 收起态:根不占任何空间,由悬浮球自行定位 */
|
|
|
|
|
|
.training-ai-sidebar.is-collapsed {
|
|
|
|
|
|
top: auto;
|
|
|
|
|
|
right: auto;
|
|
|
|
|
|
bottom: auto;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 收起态:右下角圆形悬浮球 ===== */
|
|
|
|
|
|
.ai-fab {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
right: 24px;
|
|
|
|
|
|
bottom: 28px;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
height: 50px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--ai-accent);
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(37, 99, 235, 0.32);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ai-fab__icon { font-size: 23px; color: #ffffff; }
|
|
|
|
|
|
.ai-fab:hover {
|
|
|
|
|
|
background: var(--ai-accent-deep);
|
|
|
|
|
|
box-shadow: 0 10px 28px rgba(37, 99, 235, 0.4);
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 展开态骨架:白底卡片,撑满 fixed 根 ===== */
|
|
|
|
|
|
.ai-sidebar-body {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
border: 1px solid var(--ai-border);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
/* 柔和小投影,避免在页面底色上形成灰色边缘带 */
|
|
|
|
|
|
box-shadow: 0 6px 20px rgba(17, 24, 39, 0.12);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0 8px 0 12px;
|
|
|
|
|
|
border-bottom: 1px solid var(--ai-border);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ai-head__brand {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 7px;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { color: var(--ai-accent); font-size: var(--student-fs-h1); }
|
|
|
|
|
|
}
|
|
|
|
|
|
.ai-head__close {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
span { font-size: 18px; line-height: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
&:hover { color: var(--ai-accent); background: var(--ai-soft); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 分段控件:助学 / 助评 */
|
|
|
|
|
|
.ai-segmented {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
padding: 10px 12px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ai-segmented__item {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
flex: 1 1 0;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: color 0.16s ease, background 0.16s ease, border-color 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { font-size: var(--student-fs-body); }
|
|
|
|
|
|
|
|
|
|
|
|
&:hover { color: var(--ai-text); background: var(--ai-soft); }
|
|
|
|
|
|
|
|
|
|
|
|
&.on {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--ai-accent);
|
|
|
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.22);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-pane {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
padding: 4px 12px 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 助学:对话流(AI 去气泡 / 用户浅灰气泡) ===== */
|
|
|
|
|
|
.chat-stream {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
min-height: 140px;
|
|
|
|
|
|
padding: 12px 2px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chat-line { display: flex; margin: 0; }
|
|
|
|
|
|
.chat-line--ai { align-items: flex-start; gap: 8px; }
|
|
|
|
|
|
.chat-line--user { justify-content: flex-end; }
|
|
|
|
|
|
|
|
|
|
|
|
.chat-avatar {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
margin-top: 1px;
|
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--ai-accent);
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chat-text {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chat-text--ai {
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
line-height: var(--ai-lh-body);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chat-text--user {
|
|
|
|
|
|
max-width: 82%;
|
|
|
|
|
|
padding: 8px 11px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
background: var(--ai-soft);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
line-height: var(--ai-lh-body);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 思考态:AI 正在思考 + 跳动蓝点 */
|
|
|
|
|
|
.chat-thinking {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.thinking-dots {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
|
width: 5px;
|
|
|
|
|
|
height: 5px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: #3b82f6;
|
|
|
|
|
|
animation: thinking-bounce 1.2s ease-in-out infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
i:nth-child(2) { animation-delay: 0.2s; }
|
|
|
|
|
|
i:nth-child(3) { animation-delay: 0.4s; }
|
|
|
|
|
|
}
|
|
|
|
|
|
@keyframes thinking-bounce {
|
|
|
|
|
|
0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
|
|
|
|
|
|
40% { opacity: 1; transform: translateY(-3px); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 参考提示卡片 */
|
|
|
|
|
|
.reference-card {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
padding: 9px 10px;
|
|
|
|
|
|
border: 1px solid var(--ai-border);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
color: var(--ai-text-2);
|
|
|
|
|
|
background: var(--ai-soft-2);
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: border-color 0.16s ease, background 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover { border-color: var(--ai-accent); background: var(--ai-soft); }
|
|
|
|
|
|
}
|
|
|
|
|
|
.reference-card__title {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { color: var(--ai-accent); font-size: var(--student-fs-body); }
|
|
|
|
|
|
}
|
|
|
|
|
|
.reference-card__text {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--ai-text-2);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: var(--ai-lh-caption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 一体化输入器 */
|
|
|
|
|
|
.composer {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
border: 1px solid var(--ai-border-strong);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
transition: border-color 0.16s ease, box-shadow 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:focus-within {
|
|
|
|
|
|
border-color: var(--ai-accent);
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
textarea {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
/* 单行起步:正文 22 + 上下内边距 19 = 41,取 44;高度由 JS 按内容撑开 */
|
|
|
|
|
|
min-height: 44px;
|
|
|
|
|
|
/* 与 JS 上限一致:约 6 行,超出后框内滚动 */
|
|
|
|
|
|
max-height: 168px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 10px 38px 9px 11px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
resize: none;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
line-height: var(--ai-lh-body);
|
|
|
|
|
|
|
|
|
|
|
|
&::placeholder { color: var(--ai-gray-light); }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.composer__send {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
right: 6px;
|
|
|
|
|
|
bottom: 6px;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--ai-accent);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { font-size: var(--student-fs-h2); }
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) { background: var(--ai-accent-deep); }
|
|
|
|
|
|
&:disabled { cursor: not-allowed; opacity: 0.45; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 助评:分数行 + 明细 ===== */
|
|
|
|
|
|
.score-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 12px 2px 10px;
|
|
|
|
|
|
border-bottom: 1px solid var(--ai-border);
|
|
|
|
|
|
}
|
|
|
|
|
|
.score-row__label {
|
|
|
|
|
|
color: var(--ai-text-2);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
}
|
|
|
|
|
|
.score-row__value { display: inline-flex; align-items: baseline; gap: 3px; }
|
|
|
|
|
|
.score-row__value strong {
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-display);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: 1.1;
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
}
|
|
|
|
|
|
.score-row__value em {
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.review-scroll {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
min-height: 120px;
|
|
|
|
|
|
padding: 4px 2px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
.review-result { display: flex; flex-direction: column; }
|
|
|
|
|
|
.review-summary {
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
line-height: var(--ai-lh-body);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 评分明细:分隔线列表,不铺底色 */
|
|
|
|
|
|
.criteria { display: flex; flex-direction: column; margin-top: 4px; }
|
|
|
|
|
|
.criterion {
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--ai-border);
|
|
|
|
|
|
}
|
|
|
|
|
|
.criterion__top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.criterion__name {
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
}
|
|
|
|
|
|
.criterion__score {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
|
|
|
|
|
|
|
em {
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.criterion__desc {
|
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: var(--ai-lh-caption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 优先改进:浅灰块 + 墨黑左条 */
|
|
|
|
|
|
.improvements {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
padding: 9px 10px 9px 11px;
|
|
|
|
|
|
border-left: 3px solid var(--ai-accent);
|
|
|
|
|
|
border-radius: 0 8px 8px 0;
|
|
|
|
|
|
background: var(--ai-soft-2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.improvements__title {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: var(--ai-text);
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
}
|
|
|
|
|
|
.improvements ul {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
|
padding-left: 16px;
|
|
|
|
|
|
color: var(--ai-text-2);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: var(--ai-lh-caption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 空状态 */
|
|
|
|
|
|
.review-empty {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-height: 180px;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon { color: var(--ai-gray-light); font-size: 22px; }
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: var(--ai-lh-caption);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.review-hint {
|
|
|
|
|
|
margin: 10px 0 0;
|
|
|
|
|
|
color: var(--ai-gray);
|
|
|
|
|
|
font-size: var(--ai-fs-caption);
|
|
|
|
|
|
line-height: var(--ai-lh-caption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 助评主按钮:墨黑实底 */
|
|
|
|
|
|
.review-primary {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
border: 1px solid var(--ai-accent);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--ai-accent);
|
|
|
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.22);
|
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
|
font-size: var(--ai-fs-body);
|
|
|
|
|
|
font-weight: var(--ai-fw-emphasized);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: background 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(:disabled) {
|
|
|
|
|
|
border-color: var(--ai-accent-deep);
|
|
|
|
|
|
background: var(--ai-accent-deep);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.28);
|
|
|
|
|
|
}
|
|
|
|
|
|
&:disabled { cursor: not-allowed; opacity: 0.5; box-shadow: none; }
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|