Przeglądaj źródła

style: 对齐消息工具与附件展示

samien 2 miesięcy temu
rodzic
commit
905a058b31

+ 10 - 1
src/client/components/messages/MessageTimeline.vue

@@ -155,11 +155,19 @@ defineExpose({ scrollToMessage, scrollToBottom })
               <MarkdownContent :content="message.reasoning" />
               <MarkdownContent :content="message.reasoning" />
             </details>
             </details>
 
 
+            <div v-if="message.role === 'user' && message.attachments?.length" class="message__attachments">
+              <button v-for="attachment in message.attachments" :key="attachment.id" type="button" @click="emit('preview', attachment)">
+                <img v-if="attachment.kind === 'image' && attachment.url" :src="attachment.url" :alt="attachment.name" />
+                <span v-else><AppIcon :name="attachment.kind || 'file'" :size="17" /></span>
+                <strong>{{ attachment.name }}</strong>
+              </button>
+            </div>
+
             <div class="message__content">
             <div class="message__content">
               <MarkdownContent :content="message.content" :streaming="message.status === 'streaming'" />
               <MarkdownContent :content="message.content" :streaming="message.status === 'streaming'" />
             </div>
             </div>
 
 
-            <div v-if="message.attachments?.length" class="message__attachments">
+            <div v-if="message.role !== 'user' && message.attachments?.length" class="message__attachments">
               <button v-for="attachment in message.attachments" :key="attachment.id" type="button" @click="emit('preview', attachment)">
               <button v-for="attachment in message.attachments" :key="attachment.id" type="button" @click="emit('preview', attachment)">
                 <img v-if="attachment.kind === 'image' && attachment.url" :src="attachment.url" :alt="attachment.name" />
                 <img v-if="attachment.kind === 'image' && attachment.url" :src="attachment.url" :alt="attachment.name" />
                 <span v-else><AppIcon :name="attachment.kind || 'file'" :size="17" /></span>
                 <span v-else><AppIcon :name="attachment.kind || 'file'" :size="17" /></span>
@@ -222,6 +230,7 @@ defineExpose({ scrollToMessage, scrollToBottom })
 .message__reasoning { max-width: 420px; margin: 3px 0 10px; padding: 0 0 8px; border: 0; border-bottom: 1px dashed var(--line-strong); color: var(--text-secondary); font-size: 11px; }.message__reasoning summary { display: flex; align-items: center; gap: 5px; list-style: none; color: var(--text-muted); cursor: pointer; font-size: 9px; }.message__reasoning summary::-webkit-details-marker { display: none; }.message__reasoning summary::before { content: '›'; color: var(--text-muted); font-size: 14px; line-height: 1; transition: transform 120ms ease; }.message__reasoning[open] summary::before { transform: rotate(90deg); }.message__reasoning :deep(.markdown) { margin-top: 8px; }
 .message__reasoning { max-width: 420px; margin: 3px 0 10px; padding: 0 0 8px; border: 0; border-bottom: 1px dashed var(--line-strong); color: var(--text-secondary); font-size: 11px; }.message__reasoning summary { display: flex; align-items: center; gap: 5px; list-style: none; color: var(--text-muted); cursor: pointer; font-size: 9px; }.message__reasoning summary::-webkit-details-marker { display: none; }.message__reasoning summary::before { content: '›'; color: var(--text-muted); font-size: 14px; line-height: 1; transition: transform 120ms ease; }.message__reasoning[open] summary::before { transform: rotate(90deg); }.message__reasoning :deep(.markdown) { margin-top: 8px; }
 .fork-divider { display: flex; align-items: center; gap: 12px; margin: 8px 0 28px; color: var(--text-muted); font-size: 10px; }.fork-divider::before, .fork-divider::after { height: 1px; flex: 1; background: var(--line); content: ''; }.fork-divider > span { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-raised); white-space: nowrap; }.fork-divider strong { max-width: 180px; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; }
 .fork-divider { display: flex; align-items: center; gap: 12px; margin: 8px 0 28px; color: var(--text-muted); font-size: 10px; }.fork-divider::before, .fork-divider::after { height: 1px; flex: 1; background: var(--line); content: ''; }.fork-divider > span { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-raised); white-space: nowrap; }.fork-divider strong { max-width: 180px; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; }
 .message__attachments { display: flex; margin-top: 8px; flex-wrap: wrap; gap: 7px; }.message__attachments button { display: flex; min-width: 90px; max-width: 210px; min-height: 42px; align-items: center; gap: 7px; padding: 5px 8px; overflow: hidden; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); color: var(--text-secondary); cursor: pointer; }.message__attachments button:hover { border-color: var(--line-strong); }.message__attachments img { width: 48px; height: 48px; margin: -5px 0 -5px -8px; object-fit: cover; }.message__attachments button > span { display: grid; place-items: center; width: 25px; height: 25px; border-radius: 7px; background: var(--surface-soft); }.message__attachments strong { min-width: 0; overflow: hidden; font-size: 10px; font-weight: 520; text-overflow: ellipsis; white-space: nowrap; }
 .message__attachments { display: flex; margin-top: 8px; flex-wrap: wrap; gap: 7px; }.message__attachments button { display: flex; min-width: 90px; max-width: 210px; min-height: 42px; align-items: center; gap: 7px; padding: 5px 8px; overflow: hidden; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); color: var(--text-secondary); cursor: pointer; }.message__attachments button:hover { border-color: var(--line-strong); }.message__attachments img { width: 48px; height: 48px; margin: -5px 0 -5px -8px; object-fit: cover; }.message__attachments button > span { display: grid; place-items: center; width: 25px; height: 25px; border-radius: 7px; background: var(--surface-soft); }.message__attachments strong { min-width: 0; overflow: hidden; font-size: 10px; font-weight: 520; text-overflow: ellipsis; white-space: nowrap; }
+.message--user .message__attachments { margin: 0 0 9px; }.message--user .message__attachments button { width: 100%; max-width: none; min-height: 50px; padding: 7px 9px; border: 0; border-radius: 11px; background: rgba(255,255,255,.48); color: var(--text-primary); }.message--user .message__attachments button > span { width: 28px; height: 28px; background: rgba(255,255,255,.65); }.message--user .message__attachments strong { font-size: 11px; font-weight: 560; }.dark .message--user .message__attachments button { background: rgba(255,255,255,.08); }
 .message__tools { margin-top: 9px; }.message__error { display: flex; align-items: center; gap: 5px; margin: 7px 0 0; color: var(--danger); font-size: 9px; }
 .message__tools { margin-top: 9px; }.message__error { display: flex; align-items: center; gap: 5px; margin: 7px 0 0; color: var(--danger); font-size: 9px; }
 .message__actions { display: flex; position: absolute; left: -3px; top: 100%; gap: 2px; padding-top: 2px; opacity: 0; transition: opacity 120ms ease; }.message:hover .message__actions, .message:focus-within .message__actions { opacity: 1; }.message--user .message__actions { right: 0; left: auto; }.message__actions button { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 7px; background: transparent; color: var(--text-muted); cursor: pointer; }.message__actions button:hover { background: var(--surface-soft); color: var(--text-primary); }
 .message__actions { display: flex; position: absolute; left: -3px; top: 100%; gap: 2px; padding-top: 2px; opacity: 0; transition: opacity 120ms ease; }.message:hover .message__actions, .message:focus-within .message__actions { opacity: 1; }.message--user .message__actions { right: 0; left: auto; }.message__actions button { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 7px; background: transparent; color: var(--text-muted); cursor: pointer; }.message__actions button:hover { background: var(--surface-soft); color: var(--text-primary); }
 .message--failed .message__body { border-color: color-mix(in srgb, var(--danger) 35%, var(--line)); }
 .message--failed .message__body { border-color: color-mix(in srgb, var(--danger) 35%, var(--line)); }

+ 11 - 15
src/client/components/messages/ToolTrace.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 import { computed, ref } from 'vue'
-import AppIcon from '@/components/common/AppIcon.vue'
 import type { UiToolCall } from './types'
 import type { UiToolCall } from './types'
 
 
 const props = defineProps<{ tool: UiToolCall }>()
 const props = defineProps<{ tool: UiToolCall }>()
@@ -16,24 +15,21 @@ const detail = computed(() => {
 <template>
 <template>
   <div class="tool-trace" :class="`tool-trace--${tool.status}`">
   <div class="tool-trace" :class="`tool-trace--${tool.status}`">
     <button type="button" @click="open = !open">
     <button type="button" @click="open = !open">
-      <span class="tool-trace__icon"><AppIcon name="tools" :size="14" /></span>
-      <span class="tool-trace__copy"><strong>{{ tool.name }}</strong><small>{{ statusLabel }}<template v-if="tool.durationMs"> · {{ tool.durationMs }} ms</template></small></span>
-      <span v-if="tool.status === 'running'" class="tool-trace__pulse" />
-      <AppIcon name="chevron-down" :size="13" :class="{ rotated: open }" />
+      <span class="tool-trace__caret" :class="{ open }">›</span>
+      <strong>{{ tool.name }}</strong>
+      <small v-if="tool.status === 'error'">{{ statusLabel }}</small>
     </button>
     </button>
     <pre v-if="open && detail">{{ detail }}</pre>
     <pre v-if="open && detail">{{ detail }}</pre>
   </div>
   </div>
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-.tool-trace { margin: 7px 0; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); overflow: hidden; }
-.tool-trace > button { display: flex; width: 100%; min-height: 42px; align-items: center; gap: 9px; padding: 6px 9px; border: 0; background: transparent; color: var(--text-secondary); cursor: pointer; text-align: left; }
-.tool-trace > button:hover { background: var(--surface-soft); }
-.tool-trace__icon { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 7px; background: var(--surface-soft); }
-.tool-trace__copy { display: flex; min-width: 0; flex: 1; flex-direction: column; }.tool-trace__copy strong { color: var(--text-primary); font-size: 10px; font-weight: 580; }.tool-trace__copy small { margin-top: 2px; color: var(--text-muted); font-size: 9px; }
-.tool-trace__pulse { width: 5px; height: 5px; border-radius: 50%; background: var(--warning); animation: pulse 1s ease infinite; }
-.rotated { transform: rotate(180deg); }.app-icon { transition: transform 140ms ease; }
-pre { max-height: 260px; margin: 0; padding: 10px 12px; overflow: auto; border-top: 1px solid var(--line); background: var(--surface-soft); color: var(--text-secondary); font: 10px/1.55 var(--font-code); white-space: pre-wrap; overflow-wrap: anywhere; }
-.tool-trace--error { border-color: color-mix(in srgb, var(--danger) 30%, var(--line)); }
-@keyframes pulse { 50% { opacity: .3; } }
+.tool-trace { margin: 7px 0; color: var(--text-muted); }
+.tool-trace > button { display: inline-flex; min-height: 22px; align-items: center; gap: 7px; padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; font: 10px/1.5 var(--font-code); text-align: left; }
+.tool-trace > button:hover { color: var(--text-secondary); }
+.tool-trace > button strong { font: inherit; font-weight: 400; letter-spacing: .01em; }
+.tool-trace > button small { color: var(--danger); font: inherit; }
+.tool-trace__caret { display: inline-block; width: 9px; color: var(--text-muted); font-size: 13px; line-height: 1; transition: transform 120ms ease; }.tool-trace__caret.open { transform: rotate(90deg); }
+pre { max-width: min(680px, 100%); max-height: 260px; margin: 4px 0 2px 16px; padding: 8px 10px; overflow: auto; border-left: 1px dashed var(--line-strong); background: transparent; color: var(--text-secondary); font: 10px/1.55 var(--font-code); white-space: pre-wrap; overflow-wrap: anywhere; }
+.tool-trace--error { color: var(--danger); }
 </style>
 </style>