@@ -15,14 +15,20 @@ const { aiDialogVisible, aiImageDialogVisible } = storeToRefs(displayStore)
1515const { toggleAIDialog, toggleAIImageDialog } = displayStore
1616
1717const store = useStore ()
18- const { editor } = storeToRefs (store )
18+ const { editor, hasShownAIToolboxHint } = storeToRefs (store )
1919
2020// 工具栏状态:false=默认(只显示贴边栏), true=展开(显示AI图标)
2121const isExpanded = ref (false ) // 默认收起状态
2222
2323// AI 工具箱相关状态
2424const toolBoxVisible = ref (false )
2525
26+ // 是否显示选中文本提示动画
27+ const showSelectionHint = ref (false )
28+ let selectionHintTimer: NodeJS .Timeout | null = null
29+ let selectionCheckInterval: NodeJS .Timeout | null = null
30+ let lastSelectedText = ` `
31+
2632// 检查选中文本的函数
2733function getSelectedText() {
2834 try {
@@ -36,6 +42,43 @@ function getSelectedText() {
3642 }
3743}
3844
45+ // 检查并更新选中文本提示
46+ function checkSelectionAndUpdateHint() {
47+ // 如果已经显示过提示,就不再显示
48+ if (hasShownAIToolboxHint .value ) {
49+ return
50+ }
51+
52+ const selected = getSelectedText ()
53+
54+ // 如果选中状态发生变化
55+ if (selected !== lastSelectedText ) {
56+ lastSelectedText = selected
57+
58+ // 清除之前的定时器
59+ if (selectionHintTimer ) {
60+ clearTimeout (selectionHintTimer )
61+ selectionHintTimer = null
62+ }
63+
64+ // 如果有选中文本且工具栏未展开
65+ if (selected && ! isExpanded .value ) {
66+ showSelectionHint .value = true
67+
68+ // 标记已经显示过提示
69+ hasShownAIToolboxHint .value = true
70+
71+ // 3秒后自动隐藏提示
72+ selectionHintTimer = setTimeout (() => {
73+ showSelectionHint .value = false
74+ }, 3000 )
75+ }
76+ else {
77+ showSelectionHint .value = false
78+ }
79+ }
80+ }
81+
3982// 动态计算是否有选中文本
4083const hasSelectedText = computed (() => {
4184 if (! editor .value || ! isExpanded .value )
@@ -51,6 +94,15 @@ const currentSelectedText = computed(() => {
5194// 切换展开/收起状态
5295function toggleExpanded() {
5396 isExpanded .value = ! isExpanded .value
97+
98+ // 展开后隐藏提示
99+ if (isExpanded .value ) {
100+ showSelectionHint .value = false
101+ if (selectionHintTimer ) {
102+ clearTimeout (selectionHintTimer )
103+ selectionHintTimer = null
104+ }
105+ }
54106}
55107
56108// 打开AI助手
@@ -70,6 +122,11 @@ function openAIToolBox() {
70122
71123// 监听编辑区点击,自动收起工具栏
72124onMounted (() => {
125+ // 启动定时检查选中文本
126+ selectionCheckInterval = setInterval (() => {
127+ checkSelectionAndUpdateHint ()
128+ }, 300 ) // 每300ms检查一次
129+
73130 const handleInteraction = (e : Event ) => {
74131 // 只有在展开状态才需要处理
75132 if (! isExpanded .value )
@@ -103,7 +160,6 @@ onMounted(() => {
103160 const shouldNotCollapse = excludeSelectors .some (selector => target .closest (selector ))
104161
105162 if (! shouldNotCollapse ) {
106- console .log (` Interaction outside excluded areas, collapsing toolbar. Event type: ${e .type } ` )
107163 isExpanded .value = false
108164 }
109165 }
@@ -115,6 +171,18 @@ onMounted(() => {
115171 onUnmounted (() => {
116172 document .removeEventListener (` click ` , handleInteraction , true )
117173 document .removeEventListener (` touchstart ` , handleInteraction , true )
174+
175+ // 清理定时器
176+ if (selectionHintTimer ) {
177+ clearTimeout (selectionHintTimer )
178+ selectionHintTimer = null
179+ }
180+
181+ // 清理轮询
182+ if (selectionCheckInterval ) {
183+ clearInterval (selectionCheckInterval )
184+ selectionCheckInterval = null
185+ }
118186 })
119187})
120188 </script >
@@ -129,10 +197,27 @@ onMounted(() => {
129197 <div
130198 v-if =" !isExpanded"
131199 class =" w-5 h-16 bg-gradient-to-b from-blue-500/90 to-purple-500/90 hover:from-blue-600/95 hover:to-purple-600/95 dark:from-blue-400/90 dark:to-purple-400/90 dark:hover:from-blue-500/95 dark:hover:to-purple-500/95 backdrop-blur-lg border-l border-y border-blue-300/50 dark:border-blue-600/50 cursor-pointer transition-all duration-200 flex items-center justify-center rounded-l-lg shadow-lg group utools-sidebar-edge"
200+ :class =" { 'animate-pulse-hint': showSelectionHint }"
132201 title =" 展开AI工具栏"
133202 @click =" toggleExpanded"
134203 >
135204 <Settings2 class =" h-4 w-4 text-white drop-shadow-sm group-hover:scale-110 transition-transform duration-200" />
205+
206+ <!-- 选中文本提示气泡 -->
207+ <Transition name =" hint-fade" >
208+ <div
209+ v-if =" showSelectionHint"
210+ class =" hint-bubble absolute right-full mr-3 px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white text-sm font-medium rounded-lg shadow-xl whitespace-nowrap pointer-events-none animate-bounce-gentle z-50"
211+ style =" top : 50% ; transform : translateY (-50% );"
212+ >
213+ <div class =" relative flex items-center gap-2" >
214+ <Wand2 class =" h-4 w-4" />
215+ <span >点击打开 AI 工具箱</span >
216+ <!-- 箭头 -->
217+ <div class =" hint-arrow absolute top-1/2 -right-2 -translate-y-1/2 w-0 h-0 border-t-[6px] border-b-[6px] border-l-[6px] border-transparent border-l-purple-500" />
218+ </div >
219+ </div >
220+ </Transition >
136221 </div >
137222
138223 <!-- 展开状态:显示AI图标 -->
@@ -236,6 +321,60 @@ onMounted(() => {
236321 overflow : visible ;
237322}
238323
324+ /* 选中文本时的脉冲动画 */
325+ @keyframes pulse-hint {
326+ 0% ,
327+ 100% {
328+ box-shadow : 0 0 0 0 rgba (59 , 130 , 246 , 0.7 );
329+ }
330+ 50% {
331+ box-shadow : 0 0 0 8px rgba (59 , 130 , 246 , 0 );
332+ }
333+ }
334+
335+ .animate-pulse-hint {
336+ animation : pulse-hint 2s cubic-bezier (0.4 , 0 , 0.6 , 1 ) infinite ;
337+ }
338+
339+ /* 提示气泡的轻微弹跳动画 */
340+ @keyframes bounce-gentle {
341+ 0% ,
342+ 100% {
343+ transform : translateX (0 );
344+ }
345+ 50% {
346+ transform : translateX (-4px );
347+ }
348+ }
349+
350+ .animate-bounce-gentle {
351+ animation : bounce-gentle 1s ease-in-out infinite ;
352+ }
353+
354+ /* 提示气泡淡入淡出过渡 */
355+ .hint-fade-enter-active ,
356+ .hint-fade-leave-active {
357+ transition :
358+ opacity 0.3s ease ,
359+ transform 0.3s ease ;
360+ }
361+
362+ .hint-fade-enter-from {
363+ opacity : 0 ;
364+ transform : translateX (8px );
365+ }
366+
367+ .hint-fade-leave-to {
368+ opacity : 0 ;
369+ transform : translateX (8px );
370+ }
371+
372+ .hint-fade-enter-to ,
373+ .hint-fade-leave-from {
374+ opacity : 1 ;
375+ transform : translateX (0 );
376+ }
377+
239378/* 响应式调整 */
240379@media (max-width : 768px ) {
241380 .editor-ai-toolbar {
@@ -330,6 +469,56 @@ onMounted(() => {
330469 color : rgb (0 0 0 ) !important ;
331470}
332471
472+ /* uTools 模式下提示气泡使用黑白风格 */
473+ .is-utools .hint-bubble {
474+ background : rgb (0 0 0 / 0.9 ) !important ;
475+ background-image : none !important ;
476+ color : rgb (255 255 255 ) !important ;
477+ }
478+
479+ .is-utools.dark .hint-bubble {
480+ background : rgb (255 255 255 / 0.9 ) !important ;
481+ background-image : none !important ;
482+ color : rgb (0 0 0 ) !important ;
483+ }
484+
485+ .is-utools .hint-arrow {
486+ border-left-color : rgb (0 0 0 / 0.9 ) !important ;
487+ }
488+
489+ .is-utools.dark .hint-arrow {
490+ border-left-color : rgb (255 255 255 / 0.9 ) !important ;
491+ }
492+
493+ /* uTools 模式下脉冲动画使用黑白风格 */
494+ @keyframes pulse-hint-utools {
495+ 0% ,
496+ 100% {
497+ box-shadow : 0 0 0 0 rgba (0 , 0 , 0 , 0.7 );
498+ }
499+ 50% {
500+ box-shadow : 0 0 0 8px rgba (0 , 0 , 0 , 0 );
501+ }
502+ }
503+
504+ @keyframes pulse-hint-utools-dark {
505+ 0% ,
506+ 100% {
507+ box-shadow : 0 0 0 0 rgba (255 , 255 , 255 , 0.7 );
508+ }
509+ 50% {
510+ box-shadow : 0 0 0 8px rgba (255 , 255 , 255 , 0 );
511+ }
512+ }
513+
514+ .is-utools .animate-pulse-hint {
515+ animation : pulse-hint-utools 2s cubic-bezier (0.4 , 0 , 0.6 , 1 ) infinite ;
516+ }
517+
518+ .is-utools.dark .animate-pulse-hint {
519+ animation : pulse-hint-utools-dark 2s cubic-bezier (0.4 , 0 , 0.6 , 1 ) infinite ;
520+ }
521+
333522/* uTools 模式下 AI 按钮使用黑白风格 */
334523.is-utools .utools-ai-button {
335524 background : rgb (0 0 0 / 0.85 ) !important ;
0 commit comments