Ver Fonte

页面样式修改

moki há 4 dias atrás
pai
commit
97341b1349

+ 86 - 0
src/components/CompletedCard.vue

@@ -0,0 +1,86 @@
+<script lang="ts" setup>
+</script>
+
+<template>
+  <div class="completed-card">
+    <div class="ring-group">
+      <div class="ring ring-1"/>
+      <div class="ring ring-2"/>
+    </div>
+    <div class="check">&#10003;</div>
+  </div>
+</template>
+
+<style scoped>
+.completed-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #0f1d14;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(82, 196, 26, 0.2);
+  box-shadow: 0 0 12px rgba(82, 196, 26, 0.08),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring-group {
+  position: absolute;
+  inset: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring {
+  position: absolute;
+  border-radius: 50%;
+  border: 2px solid #52c41a;
+  animation: expand 2.4s ease-out infinite;
+}
+
+.ring-1 {
+  animation-delay: 0s;
+}
+
+.ring-2 {
+  animation-delay: 1.2s;
+}
+
+.check {
+  position: relative;
+  font-size: 28px;
+  color: #52c41a;
+  z-index: 1;
+  animation: check-pulse 2.4s ease-in-out infinite;
+  text-shadow: 0 0 12px rgba(82, 196, 26, 0.5);
+}
+
+@keyframes expand {
+  0% {
+    width: 12px;
+    height: 12px;
+    opacity: 1;
+    border-width: 2px;
+  }
+  100% {
+    width: 100px;
+    height: 100px;
+    opacity: 0;
+    border-width: 1px;
+  }
+}
+
+@keyframes check-pulse {
+  0%, 100% {
+    opacity: 0.6;
+    transform: scale(1);
+  }
+  50% {
+    opacity: 1;
+    transform: scale(1.1);
+  }
+}
+</style>

+ 80 - 0
src/components/ErrorCard.vue

@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+</script>
+
+<template>
+  <div class="error-card">
+    <div class="pulse-ring"/>
+    <div class="cross">&#10005;</div>
+  </div>
+</template>
+
+<style scoped>
+.error-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1d0f0f;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(255, 77, 79, 0.3);
+  box-shadow: 0 0 16px rgba(255, 77, 79, 0.15),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  animation: shake 0.6s ease-in-out infinite;
+  animation-delay: 1.4s;
+}
+
+.pulse-ring {
+  position: absolute;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  border: 2px solid #ff4d4f;
+  animation: error-pulse 2s ease-out infinite;
+}
+
+.cross {
+  position: relative;
+  font-size: 28px;
+  color: #ff4d4f;
+  z-index: 1;
+  font-weight: bold;
+  animation: cross-flash 2s ease-in-out infinite;
+  text-shadow: 0 0 10px rgba(255, 77, 79, 0.6);
+}
+
+@keyframes error-pulse {
+  0% {
+    width: 20px;
+    height: 20px;
+    opacity: 1;
+  }
+  100% {
+    width: 80px;
+    height: 80px;
+    opacity: 0;
+  }
+}
+
+@keyframes cross-flash {
+  0%, 100% {
+    opacity: 0.5;
+  }
+  50% {
+    opacity: 1;
+  }
+}
+
+@keyframes shake {
+  0%, 100% {
+    transform: translateX(0);
+  }
+  25% {
+    transform: translateX(-2px);
+  }
+  75% {
+    transform: translateX(2px);
+  }
+}
+</style>

+ 45 - 0
src/components/IdleCard.vue

@@ -0,0 +1,45 @@
+<script lang="ts" setup>
+</script>
+
+<template>
+  <div class="idle-card">
+    <div class="orb"/>
+  </div>
+</template>
+
+<style scoped>
+.idle-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #0f1d14;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(82, 196, 26, 0.2);
+  box-shadow: 0 0 12px rgba(82, 196, 26, 0.08),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.orb {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background: radial-gradient(circle at 35% 35%, #95de64, #52c41a);
+  animation: breathe 4s ease-in-out infinite;
+  box-shadow: 0 0 24px rgba(82, 196, 26, 0.3),
+  0 0 8px rgba(82, 196, 26, 0.5);
+}
+
+@keyframes breathe {
+  0%, 100% {
+    transform: scale(1);
+    opacity: 0.8;
+  }
+  50% {
+    transform: scale(0.7);
+    opacity: 0.4;
+  }
+}
+</style>

+ 72 - 0
src/components/PendingCard.vue

@@ -0,0 +1,72 @@
+<script lang="ts" setup>
+</script>
+
+<template>
+  <div class="pending-card">
+    <div class="cursor-row">
+      <span class="text-line"/>
+      <span class="cursor"/>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.pending-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1d1a0f;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(250, 173, 20, 0.25);
+  box-shadow: 0 0 12px rgba(250, 173, 20, 0.08),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.cursor-row {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+}
+
+.text-line {
+  display: block;
+  width: 60px;
+  height: 3px;
+  background: linear-gradient(90deg, #faad14, #ffc53d);
+  border-radius: 2px;
+  animation: text-pulse 2s ease-in-out infinite;
+}
+
+.cursor {
+  display: block;
+  width: 3px;
+  height: 24px;
+  background: #faad14;
+  border-radius: 1px;
+  animation: blink 1s step-end infinite;
+  box-shadow: 0 0 8px rgba(250, 173, 20, 0.6);
+}
+
+@keyframes blink {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0;
+  }
+}
+
+@keyframes text-pulse {
+  0%, 100% {
+    width: 60px;
+    opacity: 0.8;
+  }
+  50% {
+    width: 80px;
+    opacity: 1;
+  }
+}
+</style>

+ 79 - 0
src/components/PermissionCard.vue

@@ -0,0 +1,79 @@
+<script lang="ts" setup>
+</script>
+
+<template>
+  <div class="permission-card">
+    <div class="shield">
+      <div class="shield-body">
+        <div class="lock-icon">
+          <div class="lock-shackle"/>
+          <div class="lock-body"/>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.permission-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1d1a0f;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(250, 173, 20, 0.25);
+  box-shadow: 0 0 12px rgba(250, 173, 20, 0.08),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.shield {
+  animation: shield-glow 2s ease-in-out infinite;
+}
+
+.shield-body {
+  width: 36px;
+  height: 42px;
+  background: linear-gradient(180deg, #ffc53d, #faad14);
+  clip-path: polygon(50% 0%, 100% 15%, 100% 65%, 50% 100%, 0% 65%, 0% 15%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 0 16px rgba(250, 173, 20, 0.4);
+}
+
+.lock-icon {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 1px;
+}
+
+.lock-shackle {
+  width: 12px;
+  height: 8px;
+  border: 2px solid #1d1a0f;
+  border-bottom: none;
+  border-radius: 6px 6px 0 0;
+}
+
+.lock-body {
+  width: 14px;
+  height: 10px;
+  background: #1d1a0f;
+  border-radius: 2px;
+}
+
+@keyframes shield-glow {
+  0%, 100% {
+    filter: drop-shadow(0 0 4px rgba(250, 173, 20, 0.3));
+    transform: scale(1);
+  }
+  50% {
+    filter: drop-shadow(0 0 12px rgba(250, 173, 20, 0.6));
+    transform: scale(1.08);
+  }
+}
+</style>

+ 99 - 0
src/components/RetryCard.vue

@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+const dotCount = 3
+</script>
+
+<template>
+  <div class="retry-card">
+    <div class="drift">
+      <div class="orbit">
+        <div
+            v-for="i in dotCount"
+            :key="i"
+            :style="{
+            animationDelay: `${(i - 1) * (2 / dotCount)}s`,
+          }"
+            class="dot"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.retry-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1d150f;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(250, 140, 22, 0.25);
+  box-shadow: 0 0 12px rgba(250, 140, 22, 0.1),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.drift {
+  animation: drift-x 3s ease-in-out infinite alternate;
+}
+
+.orbit {
+  position: relative;
+  width: 56px;
+  height: 56px;
+  animation: spin 2s linear infinite;
+}
+
+.dot {
+  position: absolute;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background: radial-gradient(circle at 35% 35%, #ffc069, #fa8c16);
+  box-shadow: 0 0 10px rgba(250, 140, 22, 0.5);
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  animation: fade-dot 2s ease-in-out infinite;
+}
+
+.dot:nth-child(2) {
+  top: 50%;
+  left: 100%;
+  transform: translate(-50%, -50%);
+}
+
+.dot:nth-child(3) {
+  top: 100%;
+  left: 50%;
+  transform: translate(-50%, -100%);
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes fade-dot {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.3;
+  }
+}
+
+@keyframes drift-x {
+  0% {
+    transform: translateX(-120%);
+  }
+  100% {
+    transform: translateX(120%);
+  }
+}
+</style>

+ 65 - 0
src/components/RunningCard.vue

@@ -0,0 +1,65 @@
+<script lang="ts" setup>
+const particleCount = 8
+</script>
+
+<template>
+  <div class="running-card">
+    <div class="stream">
+      <div
+          v-for="i in particleCount"
+          :key="i"
+          :style="{
+          animationDelay: `${(i - 1) * 0.35}s`,
+          top: `${15 + (i % 3) * 30}%`,
+        }"
+          class="particle"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.running-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #0f1a1d;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(19, 194, 194, 0.25);
+  box-shadow: 0 0 12px rgba(19, 194, 194, 0.08),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+}
+
+.stream {
+  position: absolute;
+  inset: 0;
+}
+
+.particle {
+  position: absolute;
+  left: -8px;
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #13c2c2;
+  box-shadow: 0 0 10px rgba(19, 194, 194, 0.6);
+  animation: flow 2.8s linear infinite;
+}
+
+@keyframes flow {
+  0% {
+    left: -8px;
+    opacity: 0;
+  }
+  10% {
+    opacity: 1;
+  }
+  90% {
+    opacity: 1;
+  }
+  100% {
+    left: calc(100% + 8px);
+    opacity: 0;
+  }
+}
+</style>

+ 60 - 0
src/components/SessionCompletedCard.vue

@@ -0,0 +1,60 @@
+<script lang="ts" setup>
+const ringCount = 4
+</script>
+
+<template>
+  <div class="session-card">
+    <div class="ring-group">
+      <div
+          v-for="i in ringCount"
+          :key="i"
+          :style="{ animationDelay: `${(i - 1) * 0.5}s` }"
+          class="ring"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.session-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #161616;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(140, 140, 140, 0.2);
+  box-shadow: 0 0 12px rgba(140, 140, 140, 0.05),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring-group {
+  position: absolute;
+  inset: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring {
+  position: absolute;
+  border-radius: 50%;
+  border: 1.5px solid #8c8c8c;
+  animation: fade-expand 3s ease-out infinite;
+}
+
+@keyframes fade-expand {
+  0% {
+    width: 8px;
+    height: 8px;
+    opacity: 0.8;
+  }
+  100% {
+    width: 90px;
+    height: 90px;
+    opacity: 0;
+  }
+}
+</style>

+ 68 - 0
src/components/UsingToolCard.vue

@@ -0,0 +1,68 @@
+<script lang="ts" setup>
+const ringCount = 4
+</script>
+
+<template>
+  <div class="tool-card">
+    <div class="ripple-container">
+      <div
+          v-for="i in ringCount"
+          :key="i"
+          :style="{
+          animationDelay: `${(i - 1) * 0.6}s`,
+        }"
+          class="ring"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.tool-card {
+  position: relative;
+  aspect-ratio: 3 / 1;
+  background: #1d150f;
+  border-radius: 8px;
+  overflow: hidden;
+  border: 1px solid rgba(250, 140, 22, 0.25);
+  box-shadow: 0 0 12px rgba(250, 140, 22, 0.1),
+  inset 0 0 20px rgba(0, 0, 0, 0.3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ripple-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  border: 2px solid #fa8c16;
+  animation: ripple 2.4s ease-out infinite;
+  box-shadow: 0 0 6px rgba(250, 140, 22, 0.3);
+}
+
+@keyframes ripple {
+  0% {
+    width: 16px;
+    height: 16px;
+    opacity: 1;
+    border-width: 2px;
+  }
+  100% {
+    width: 120px;
+    height: 120px;
+    opacity: 0;
+    border-width: 1px;
+  }
+}
+</style>

+ 18 - 0
src/views/Dashboard.vue

@@ -6,6 +6,15 @@ import type {PortState, StatusMessage} from '@/types'
 import PortStatusCard from '@/components/PortStatusCard.vue'
 import ReasoningCard from '@/components/ReasoningCard.vue'
 import BusyCard from '@/components/BusyCard.vue'
+import IdleCard from '@/components/IdleCard.vue'
+import RetryCard from '@/components/RetryCard.vue'
+import PendingCard from '@/components/PendingCard.vue'
+import UsingToolCard from '@/components/UsingToolCard.vue'
+import RunningCard from '@/components/RunningCard.vue'
+import CompletedCard from '@/components/CompletedCard.vue'
+import SessionCompletedCard from '@/components/SessionCompletedCard.vue'
+import PermissionCard from '@/components/PermissionCard.vue'
+import ErrorCard from '@/components/ErrorCard.vue'
 
 const { connected, onMessage, onDisconnect } = useSSE('/api/events')
 
@@ -118,6 +127,15 @@ onUnmounted(removeDisconnectListener)
       <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;">
         <ReasoningCard style="grid-column: span 2;"/>
         <BusyCard style="grid-column: span 2;"/>
+        <IdleCard style="grid-column: span 2;"/>
+        <RetryCard style="grid-column: span 2;"/>
+        <PendingCard style="grid-column: span 2;"/>
+        <UsingToolCard style="grid-column: span 2;"/>
+        <RunningCard style="grid-column: span 2;"/>
+        <CompletedCard style="grid-column: span 2;"/>
+        <SessionCompletedCard style="grid-column: span 2;"/>
+        <PermissionCard style="grid-column: span 2;"/>
+        <ErrorCard style="grid-column: span 2;"/>
       </div>
     </div>
   </div>