|
@@ -22,6 +22,15 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- 已选技能引用标签 -->
|
|
|
|
|
+ <div v-if="selectedSkill" class="skill-chips">
|
|
|
|
|
+ <div class="skill-chip">
|
|
|
|
|
+ <div class="skill-chip-icon"></div>
|
|
|
|
|
+ <span class="skill-chip-name">{{ selectedSkill.display_name }}</span>
|
|
|
|
|
+ <CloseOutlined class="skill-chip-remove" @click.stop="removeSkill()" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<a-textarea
|
|
<a-textarea
|
|
|
ref="textareaRef"
|
|
ref="textareaRef"
|
|
|
:value="modelValue"
|
|
:value="modelValue"
|
|
@@ -56,6 +65,10 @@
|
|
|
<div class="popup-item-icon session-icon"></div>
|
|
<div class="popup-item-icon session-icon"></div>
|
|
|
<span class="popup-item-text">插入 # 会话</span>
|
|
<span class="popup-item-text">插入 # 会话</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="popup-item" @click="handleInsertSkill">
|
|
|
|
|
+ <div class="popup-item-icon skill-icon"></div>
|
|
|
|
|
+ <span class="popup-item-text">插入 $ 技能</span>
|
|
|
|
|
+ </div>
|
|
|
<!-- <div class="popup-item" @click="handleInsertCommand">
|
|
<!-- <div class="popup-item" @click="handleInsertCommand">
|
|
|
<div class="popup-item-icon command-icon"></div>
|
|
<div class="popup-item-icon command-icon"></div>
|
|
|
<span class="popup-item-text">插入 / 命令</span>
|
|
<span class="popup-item-text">插入 / 命令</span>
|
|
@@ -227,15 +240,45 @@
|
|
|
<span v-else>点击选择会话(最多5个)</span>
|
|
<span v-else>点击选择会话(最多5个)</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- 技能列表弹框 -->
|
|
|
|
|
+ <div v-if="showSkillPopup" ref="skillPopupRef" class="skill-popup" @click.stop @pointerdown.stop>
|
|
|
|
|
+ <div class="skill-popup-header">
|
|
|
|
|
+ <span>选择技能</span>
|
|
|
|
|
+ <span class="skill-count">0/1</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="skill-list">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(skill, index) in filteredSkillList"
|
|
|
|
|
+ :key="skill.name"
|
|
|
|
|
+ class="skill-item"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ active: index === activeSkillIndex,
|
|
|
|
|
+ selected: isSkillSelected(skill.name),
|
|
|
|
|
+ }"
|
|
|
|
|
+ @click="handleSelectSkill(skill)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="skill-item-checkbox" :class="{ checked: isSkillSelected(skill.name) }">
|
|
|
|
|
+ <span v-if="isSkillSelected(skill.name)" class="check-mark">✓</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="skill-item-icon"></div>
|
|
|
|
|
+ <span class="skill-item-name">{{ skill.display_name }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="filteredSkillList.length === 0" class="skill-empty">暂无匹配技能</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="skill-popup-footer">
|
|
|
|
|
+ <span>点击选择技能(仅可选1个)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
- import { ref, computed, watch, nextTick } from 'vue';
|
|
|
|
|
|
|
+ import { ref, computed, watch, nextTick, onMounted } from 'vue';
|
|
|
import { onClickOutside } from '@vueuse/core';
|
|
import { onClickOutside } from '@vueuse/core';
|
|
|
import { CloseOutlined } from '@ant-design/icons-vue';
|
|
import { CloseOutlined } from '@ant-design/icons-vue';
|
|
|
import { formatFileSize } from './utils';
|
|
import { formatFileSize } from './utils';
|
|
|
|
|
+ import { getSkills } from '../../api';
|
|
|
import type { AttachedFile, Task } from './types';
|
|
import type { AttachedFile, Task } from './types';
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
const props = withDefaults(
|
|
@@ -277,6 +320,7 @@
|
|
|
(e: 'think-level-change', levelKey: string): void;
|
|
(e: 'think-level-change', levelKey: string): void;
|
|
|
(e: 'edit-mode-change', modeKey: string): void;
|
|
(e: 'edit-mode-change', modeKey: string): void;
|
|
|
(e: 'update:selectedSessions', sessions: Array<{ id: string; name: string }>): void;
|
|
(e: 'update:selectedSessions', sessions: Array<{ id: string; name: string }>): void;
|
|
|
|
|
+ (e: 'update:selectedSkill', skill: { name: string; display_name: string } | null): void;
|
|
|
}>();
|
|
}>();
|
|
|
|
|
|
|
|
const handleSelectModel = (item: ModelOption) => {
|
|
const handleSelectModel = (item: ModelOption) => {
|
|
@@ -467,6 +511,42 @@
|
|
|
let isRemovingTag = false;
|
|
let isRemovingTag = false;
|
|
|
let sessionJustSelected = false;
|
|
let sessionJustSelected = false;
|
|
|
|
|
|
|
|
|
|
+ // 技能列表弹框
|
|
|
|
|
+ interface SkillItem {
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ display_name: string;
|
|
|
|
|
+ path: string;
|
|
|
|
|
+ description: string;
|
|
|
|
|
+ enabled: boolean;
|
|
|
|
|
+ agents: string[];
|
|
|
|
|
+ has_skill_md: boolean;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const skillPopupRef = ref<HTMLElement>();
|
|
|
|
|
+ const showSkillPopup = ref(false);
|
|
|
|
|
+ const activeSkillIndex = ref(0);
|
|
|
|
|
+ const selectedSkill = ref<{ name: string; display_name: string } | null>(null);
|
|
|
|
|
+ const skillPopupManual = ref(false);
|
|
|
|
|
+ const skillList = ref<SkillItem[]>([]);
|
|
|
|
|
+ let skillJustSelected = false;
|
|
|
|
|
+
|
|
|
|
|
+ const fetchSkillList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getSkills();
|
|
|
|
|
+ skillList.value = res?.skills || [];
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ skillList.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ fetchSkillList();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const isSkillSelected = (name: string) => {
|
|
|
|
|
+ return selectedSkill.value?.name === name;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const isSessionSelected = (sessionId: string) => {
|
|
const isSessionSelected = (sessionId: string) => {
|
|
|
return selectedSessions.value.some((s) => s.id === sessionId);
|
|
return selectedSessions.value.some((s) => s.id === sessionId);
|
|
|
};
|
|
};
|
|
@@ -494,20 +574,62 @@
|
|
|
if (val) activeSessionIndex.value = 0;
|
|
if (val) activeSessionIndex.value = 0;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ const skillSearchText = computed(() => {
|
|
|
|
|
+ const val = props.modelValue || '';
|
|
|
|
|
+ const dollarIndex = val.lastIndexOf('$');
|
|
|
|
|
+ if (dollarIndex === -1) return '';
|
|
|
|
|
+ const afterDollar = val.substring(dollarIndex + 1);
|
|
|
|
|
+ if (afterDollar.includes(' ')) return '';
|
|
|
|
|
+ return afterDollar.toLowerCase();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const filteredSkillList = computed(() => {
|
|
|
|
|
+ if (!skillSearchText.value) return skillList.value;
|
|
|
|
|
+ return skillList.value.filter(
|
|
|
|
|
+ (s) => s.display_name.toLowerCase().includes(skillSearchText.value) || s.name.toLowerCase().includes(skillSearchText.value)
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ watch(filteredSkillList, () => {
|
|
|
|
|
+ activeSkillIndex.value = 0;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ watch(showSkillPopup, (val) => {
|
|
|
|
|
+ if (val) activeSkillIndex.value = 0;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
const handleSessionKeydown = (e: KeyboardEvent) => {
|
|
const handleSessionKeydown = (e: KeyboardEvent) => {
|
|
|
- if (!showSessionPopup.value || filteredSessionList.value.length === 0) return;
|
|
|
|
|
- if (e.key === 'ArrowDown') {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- activeSessionIndex.value = (activeSessionIndex.value + 1) % filteredSessionList.value.length;
|
|
|
|
|
- } else if (e.key === 'ArrowUp') {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- activeSessionIndex.value = (activeSessionIndex.value - 1 + filteredSessionList.value.length) % filteredSessionList.value.length;
|
|
|
|
|
- } else if (e.key === 'Enter') {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- const task = filteredSessionList.value[activeSessionIndex.value];
|
|
|
|
|
- if (task) {
|
|
|
|
|
- sessionJustSelected = true;
|
|
|
|
|
- handleSelectSession(task);
|
|
|
|
|
|
|
+ if (showSkillPopup.value && filteredSkillList.value.length > 0) {
|
|
|
|
|
+ if (e.key === 'ArrowDown') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ activeSkillIndex.value = (activeSkillIndex.value + 1) % filteredSkillList.value.length;
|
|
|
|
|
+ } else if (e.key === 'ArrowUp') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ activeSkillIndex.value = (activeSkillIndex.value - 1 + filteredSkillList.value.length) % filteredSkillList.value.length;
|
|
|
|
|
+ } else if (e.key === 'Enter') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const skill = filteredSkillList.value[activeSkillIndex.value];
|
|
|
|
|
+ if (skill) {
|
|
|
|
|
+ skillJustSelected = true;
|
|
|
|
|
+ handleSelectSkill(skill);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (showSessionPopup.value && filteredSessionList.value.length > 0) {
|
|
|
|
|
+ if (e.key === 'ArrowDown') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ activeSessionIndex.value = (activeSessionIndex.value + 1) % filteredSessionList.value.length;
|
|
|
|
|
+ } else if (e.key === 'ArrowUp') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ activeSessionIndex.value = (activeSessionIndex.value - 1 + filteredSessionList.value.length) % filteredSessionList.value.length;
|
|
|
|
|
+ } else if (e.key === 'Enter') {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ const task = filteredSessionList.value[activeSessionIndex.value];
|
|
|
|
|
+ if (task) {
|
|
|
|
|
+ sessionJustSelected = true;
|
|
|
|
|
+ handleSelectSession(task);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -538,6 +660,47 @@
|
|
|
emit('update:selectedSessions', [...selectedSessions.value]);
|
|
emit('update:selectedSessions', [...selectedSessions.value]);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ const handleInsertSkill = () => {
|
|
|
|
|
+ showPopup.value = false;
|
|
|
|
|
+ showSessionPopup.value = false;
|
|
|
|
|
+ skillPopupManual.value = true;
|
|
|
|
|
+ showSkillPopup.value = true;
|
|
|
|
|
+ const cur = props.modelValue || '';
|
|
|
|
|
+ const needsSpace = cur && !cur.endsWith(' ') ? ' ' : '';
|
|
|
|
|
+ emit('update:modelValue', `${cur}${needsSpace}$`);
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ const el = textareaRef.value?.$el?.querySelector('textarea') || textareaRef.value?.$el;
|
|
|
|
|
+ if (el) el.focus();
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSelectSkill = (skill: SkillItem) => {
|
|
|
|
|
+ selectedSkill.value = { name: skill.name, display_name: skill.display_name };
|
|
|
|
|
+ removeSkillSearchTag();
|
|
|
|
|
+ showSkillPopup.value = false;
|
|
|
|
|
+ skillPopupManual.value = false;
|
|
|
|
|
+ emit('update:selectedSkill', { name: skill.name, display_name: skill.display_name });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const removeSkillSearchTag = () => {
|
|
|
|
|
+ isRemovingTag = true;
|
|
|
|
|
+ const val = props.modelValue || '';
|
|
|
|
|
+ const dollarIndex = val.lastIndexOf('$');
|
|
|
|
|
+ if (dollarIndex === -1) {
|
|
|
|
|
+ isRemovingTag = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ emit('update:modelValue', val.substring(0, dollarIndex).trimEnd());
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ isRemovingTag = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const removeSkill = () => {
|
|
|
|
|
+ selectedSkill.value = null;
|
|
|
|
|
+ emit('update:selectedSkill', null);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const removeSearchTag = () => {
|
|
const removeSearchTag = () => {
|
|
|
isRemovingTag = true;
|
|
isRemovingTag = true;
|
|
|
const val = props.modelValue || '';
|
|
const val = props.modelValue || '';
|
|
@@ -556,7 +719,10 @@
|
|
|
() => props.modelValue,
|
|
() => props.modelValue,
|
|
|
(newVal, oldVal) => {
|
|
(newVal, oldVal) => {
|
|
|
if (isRemovingTag) return;
|
|
if (isRemovingTag) return;
|
|
|
- if (oldVal && !newVal) selectedSessions.value = [];
|
|
|
|
|
|
|
+ if (oldVal && !newVal) {
|
|
|
|
|
+ selectedSessions.value = [];
|
|
|
|
|
+ selectedSkill.value = null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
@@ -567,16 +733,36 @@
|
|
|
|
|
|
|
|
const handleTextareaInput = (val: string) => {
|
|
const handleTextareaInput = (val: string) => {
|
|
|
emit('update:modelValue', val);
|
|
emit('update:modelValue', val);
|
|
|
- if (sessionPopupManual.value) return;
|
|
|
|
|
- const hashIndex = val.lastIndexOf('#');
|
|
|
|
|
- if (hashIndex === -1) {
|
|
|
|
|
- showSessionPopup.value = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- const afterHash = val.substring(hashIndex + 1);
|
|
|
|
|
- if (afterHash.includes(' ')) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 技能弹框 $ 检测
|
|
|
|
|
+ if (!skillPopupManual.value) {
|
|
|
|
|
+ const dollarIndex = val.lastIndexOf('$');
|
|
|
|
|
+ if (dollarIndex === -1) {
|
|
|
|
|
+ showSkillPopup.value = false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const afterDollar = val.substring(dollarIndex + 1);
|
|
|
|
|
+ if (afterDollar.includes(' ')) {
|
|
|
|
|
+ showSkillPopup.value = false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showSkillPopup.value = true;
|
|
|
|
|
+ showSessionPopup.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 会话弹框 # 检测
|
|
|
|
|
+ if (!sessionPopupManual.value) {
|
|
|
|
|
+ const hashIndex = val.lastIndexOf('#');
|
|
|
|
|
+ if (hashIndex === -1) {
|
|
|
showSessionPopup.value = false;
|
|
showSessionPopup.value = false;
|
|
|
} else {
|
|
} else {
|
|
|
- showSessionPopup.value = true;
|
|
|
|
|
|
|
+ const afterHash = val.substring(hashIndex + 1);
|
|
|
|
|
+ if (afterHash.includes(' ')) {
|
|
|
|
|
+ showSessionPopup.value = false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showSessionPopup.value = true;
|
|
|
|
|
+ showSkillPopup.value = false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -584,7 +770,11 @@
|
|
|
const handleBackspace = (e: KeyboardEvent) => {
|
|
const handleBackspace = (e: KeyboardEvent) => {
|
|
|
const val = props.modelValue || '';
|
|
const val = props.modelValue || '';
|
|
|
if (val) return;
|
|
if (val) return;
|
|
|
- if (selectedSessions.value.length > 0) {
|
|
|
|
|
|
|
+ if (selectedSkill.value) {
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ selectedSkill.value = null;
|
|
|
|
|
+ emit('update:selectedSkill', null);
|
|
|
|
|
+ } else if (selectedSessions.value.length > 0) {
|
|
|
e.preventDefault();
|
|
e.preventDefault();
|
|
|
selectedSessions.value.pop();
|
|
selectedSessions.value.pop();
|
|
|
emit('update:selectedSessions', [...selectedSessions.value]);
|
|
emit('update:selectedSessions', [...selectedSessions.value]);
|
|
@@ -596,16 +786,25 @@
|
|
|
sessionPopupManual.value = false;
|
|
sessionPopupManual.value = false;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ onClickOutside(skillPopupRef, () => {
|
|
|
|
|
+ showSkillPopup.value = false;
|
|
|
|
|
+ skillPopupManual.value = false;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
const handleInsertCommand = () => {
|
|
const handleInsertCommand = () => {
|
|
|
showPopup.value = false;
|
|
showPopup.value = false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleEnterKey = (e: KeyboardEvent) => {
|
|
const handleEnterKey = (e: KeyboardEvent) => {
|
|
|
- if (showSessionPopup.value) return;
|
|
|
|
|
|
|
+ if (showSessionPopup.value || showSkillPopup.value) return;
|
|
|
if (sessionJustSelected) {
|
|
if (sessionJustSelected) {
|
|
|
sessionJustSelected = false;
|
|
sessionJustSelected = false;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (skillJustSelected) {
|
|
|
|
|
+ skillJustSelected = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (!e.shiftKey && props.modelValue.trim() && !props.loading) {
|
|
if (!e.shiftKey && props.modelValue.trim() && !props.loading) {
|
|
|
emit('send');
|
|
emit('send');
|
|
|
}
|
|
}
|
|
@@ -837,6 +1036,10 @@
|
|
|
background-image: var(--img-chat-popup-session-icon);
|
|
background-image: var(--img-chat-popup-session-icon);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .skill-icon {
|
|
|
|
|
+ background-image: var(--img-chat-options-skill-icon);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.command-icon {
|
|
.command-icon {
|
|
|
background-image: var(--img-chat-popup-command-icon);
|
|
background-image: var(--img-chat-popup-command-icon);
|
|
|
}
|
|
}
|
|
@@ -1340,6 +1543,54 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ .skill-chips {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+
|
|
|
|
|
+ .skill-chip {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ padding: 3px 6px 3px 4px;
|
|
|
|
|
+ background: rgba(10, 132, 255, 0.15);
|
|
|
|
|
+ border: 1px solid rgba(10, 132, 255, 0.3);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #e0e6ed;
|
|
|
|
|
+ cursor: default;
|
|
|
|
|
+
|
|
|
|
|
+ .skill-chip-icon {
|
|
|
|
|
+ width: 16px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ background-image: var(--img-chat-options-skill-icon);
|
|
|
|
|
+ background-repeat: no-repeat;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-chip-name {
|
|
|
|
|
+ max-width: 150px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-chip-remove {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ color: #8b949e;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ padding: 0 2px;
|
|
|
|
|
+ transition: color 0.2s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ color: #ff4d4f;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.session-popup {
|
|
.session-popup {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
bottom: 100%;
|
|
bottom: 100%;
|
|
@@ -1469,5 +1720,122 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .skill-popup {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ bottom: 100%;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ background: #0a2a3f;
|
|
|
|
|
+ border: 1px solid #1a4a6f;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ max-height: 300px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ z-index: 1000;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
|
|
|
|
+
|
|
|
|
|
+ .skill-popup-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 10px 14px 8px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #e0e6ed;
|
|
|
|
|
+ border-bottom: 1px solid rgba(63, 80, 106, 0.3);
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+
|
|
|
|
|
+ .skill-count {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #3a8fd4;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-list {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: 6px;
|
|
|
|
|
+
|
|
|
|
|
+ .skill-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ padding: 8px 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ transition: background 0.2s;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background: rgba(10, 132, 255, 0.15);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ background: rgba(10, 132, 255, 0.2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.selected {
|
|
|
|
|
+ background: rgba(10, 132, 255, 0.12);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-item-checkbox {
|
|
|
|
|
+ width: 16px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ border: 1.5px solid rgba(255, 255, 255, 0.25);
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+
|
|
|
|
|
+ &.checked {
|
|
|
|
|
+ background: #3a8fd4;
|
|
|
|
|
+ border-color: #3a8fd4;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .check-mark {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-item-icon {
|
|
|
|
|
+ width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ background-image: var(--img-chat-options-skill-icon);
|
|
|
|
|
+ background-repeat: no-repeat;
|
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-item-name {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #e0e6ed;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-empty {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: #8b949e;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ padding: 20px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .skill-popup-footer {
|
|
|
|
|
+ padding: 8px 14px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #8b949e;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border-top: 1px solid rgba(63, 80, 106, 0.3);
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|