feat(code): add Esc to exit code mode, project indicator pill, design token context
- Escape key exits code mode on desktop (T14) - Show active project name pill near chat input when in code mode (T15) - Inject selected design tokens, files, and open file content into AI system prompt when in code mode (T16) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
decee9163b
commit
ba0320fa8d
@@ -103,6 +103,24 @@
|
||||
class="flex-1 min-h-0"
|
||||
/>
|
||||
|
||||
<!-- Code mode project indicator -->
|
||||
<div
|
||||
v-if="codeContext.isCodeMode.value && codeContext.activeProject.value"
|
||||
class="px-3 pb-1 flex items-center gap-1.5"
|
||||
>
|
||||
<span class="text-[10px] px-2 py-0.5 rounded-md bg-accent/15 text-accent font-medium truncate max-w-[200px]">
|
||||
{{ codeContext.activeProject.value.name }}
|
||||
</span>
|
||||
<button
|
||||
class="text-white/30 hover:text-white/60 transition-colors p-0.5"
|
||||
@click="codeContext.exitCodeMode()"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ChatInput
|
||||
:disabled="isStreaming || comparison.isAnyStreaming.value"
|
||||
:streaming="isStreaming || comparison.isAnyStreaming.value"
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ImageAttachment } from '@aiui/core/types/message'
|
||||
import { usePersonaStore } from '@/stores/personas'
|
||||
import { useMemoryStore } from '@/stores/memory'
|
||||
import { useArchy } from '@/composables/useArchy'
|
||||
import { useCodeContext } from '@/composables/useCodeContext'
|
||||
|
||||
type Provider = 'claude' | 'openrouter' | 'mock'
|
||||
|
||||
@@ -350,6 +351,28 @@ function buildSystemPrompt(chatStore: ReturnType<typeof useChatStore>): string {
|
||||
const archy = useArchy()
|
||||
prompt += archy.buildArchyContext()
|
||||
|
||||
// Append code context when in code mode
|
||||
const code = useCodeContext()
|
||||
if (code.isCodeMode.value) {
|
||||
const sections: string[] = []
|
||||
if (code.activeProject.value) {
|
||||
sections.push(`**Active project:** ${code.activeProject.value.name} (${code.activeProject.value.language ?? 'Unknown'})`)
|
||||
}
|
||||
if (code.selectedDesignTokens.value.length > 0) {
|
||||
sections.push(`**Selected design tokens:** ${code.selectedDesignTokens.value.join(', ')}`)
|
||||
}
|
||||
if (code.selectedFiles.value.length > 0) {
|
||||
sections.push(`**Selected files:** ${code.selectedFiles.value.join(', ')}`)
|
||||
}
|
||||
if (code.activeFileContent.value && code.activeFile.value) {
|
||||
const content = code.activeFileContent.value.slice(0, 2000)
|
||||
sections.push(`**Open file (${code.activeFile.value}):**\n\`\`\`${code.activeFileLanguage.value}\n${content}\n\`\`\``)
|
||||
}
|
||||
if (sections.length > 0) {
|
||||
prompt += `\n\n**Code Context:**\n${sections.join('\n')}`
|
||||
}
|
||||
}
|
||||
|
||||
return prompt
|
||||
}
|
||||
|
||||
|
||||
@@ -4,24 +4,27 @@
|
||||
:class="[
|
||||
hasTrack && 'pb-[72px]'
|
||||
]"
|
||||
:style="isDark
|
||||
? { background: '#000 url(/assets/img/bg-intro-3.jpg) center center / cover no-repeat fixed' }
|
||||
: { backgroundColor: '#f5f4f1' }"
|
||||
:style="isEmbedded
|
||||
? { background: 'transparent' }
|
||||
: isDark
|
||||
? { background: '#000 url(' + bgImageUrl + ') center center / cover no-repeat fixed' }
|
||||
: { backgroundColor: '#f5f4f1' }"
|
||||
>
|
||||
<div v-if="isDark" class="absolute inset-0 pointer-events-none bg-black/20" />
|
||||
<div v-if="isDark && !isEmbedded" class="absolute inset-0 pointer-events-none bg-black/20" />
|
||||
|
||||
<!-- Desktop layout -->
|
||||
<div class="flex-1 flex h-full p-3 md:p-4 gap-3 md:gap-4" :class="isMobile ? 'hidden' : ''">
|
||||
|
||||
<!-- Content surface (main area) -->
|
||||
<main
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex relative"
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex relative panel-slide-in"
|
||||
:class="[
|
||||
isWideDesktop
|
||||
? (panelSide === 'left' ? 'order-2' : 'order-1')
|
||||
: (panelSide === 'left' ? 'order-last' : 'order-first'),
|
||||
!isWideDesktop && hasDetailOpen && !hasGridContent && 'detail-active'
|
||||
]"
|
||||
style="animation-delay: 0.1s"
|
||||
>
|
||||
<!-- Grid/list panel (on wide desktop: always show grid; on regular: show when no detail) -->
|
||||
<div
|
||||
@@ -115,7 +118,8 @@
|
||||
<!-- Detail panel (wide desktop only — persistent, no close/back) -->
|
||||
<div
|
||||
v-if="isWideDesktop"
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex flex-col order-3 detail-persistent"
|
||||
class="flex-1 min-w-0 path-glass-card overflow-hidden flex flex-col order-3 detail-persistent panel-slide-in"
|
||||
style="animation-delay: 0.2s"
|
||||
>
|
||||
<template v-if="hasDetailOpen">
|
||||
<ErrorBoundary title="Detail view error">
|
||||
@@ -143,7 +147,8 @@
|
||||
|
||||
<!-- Chat panel -->
|
||||
<aside
|
||||
class="relative z-[100] w-80 xl:w-96 shrink-0 flex flex-col path-glass-card overflow-visible"
|
||||
class="relative z-[100] w-80 xl:w-96 shrink-0 flex flex-col path-glass-card overflow-visible panel-slide-in"
|
||||
style="animation-delay: 0s"
|
||||
:class="isWideDesktop
|
||||
? (panelSide === 'left' ? 'order-1' : 'order-2')
|
||||
: (panelSide === 'left' ? 'order-first' : 'order-last')"
|
||||
@@ -354,7 +359,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PlayerBar />
|
||||
<PlayerBar v-if="!isEmbedded" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -375,10 +380,16 @@ import { usePlayer } from '@/composables/usePlayer'
|
||||
import { useCodeContext } from '@/composables/useCodeContext'
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { activeFile: codeActiveFile, isCodeMode, clearActiveFile: clearCodeFile } = useCodeContext()
|
||||
const { activeFile: codeActiveFile, isCodeMode, exitCodeMode, clearActiveFile: clearCodeFile } = useCodeContext()
|
||||
const { hasTrack } = usePlayer()
|
||||
const { isDark } = useTheme()
|
||||
|
||||
// Detect if running inside Archy's iframe — transparent bg, hide player bar
|
||||
const isEmbedded = !!(window as unknown as Record<string, unknown>).__AIUI_EMBEDDED__
|
||||
|
||||
// Background image URL — use BASE_URL for correct path in all deployments
|
||||
const bgImageUrl = `${import.meta.env.BASE_URL}assets/img/bg-intro-3.jpg`
|
||||
|
||||
useAI()
|
||||
|
||||
const {
|
||||
@@ -441,8 +452,21 @@ const isWideDesktop = computed(() => windowWidth.value >= 1440)
|
||||
const mobileTab = ref<'chat' | 'content' | 'context'>('chat')
|
||||
|
||||
function onResize() { windowWidth.value = window.innerWidth }
|
||||
onMounted(() => window.addEventListener('resize', onResize))
|
||||
onUnmounted(() => window.removeEventListener('resize', onResize))
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && isCodeMode.value && !isMobile.value) {
|
||||
exitCodeMode()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', onResize)
|
||||
window.addEventListener('keydown', onKeydown)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onResize)
|
||||
window.removeEventListener('keydown', onKeydown)
|
||||
})
|
||||
|
||||
// Auto-switch to content tab on mobile when panel opens or content changes
|
||||
watch(panelOpen, (open) => {
|
||||
|
||||
Reference in New Issue
Block a user