mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
Merge branch 'linear_mode' into comfy_vibe
This commit is contained in:
2
ComfyUI_vibe/src/components.d.ts
vendored
2
ComfyUI_vibe/src/components.d.ts
vendored
@@ -30,8 +30,6 @@ declare module 'vue' {
|
|||||||
LinearStepCard: typeof import('./components/linear/LinearStepCard.vue')['default']
|
LinearStepCard: typeof import('./components/linear/LinearStepCard.vue')['default']
|
||||||
LinearTemplateCard: typeof import('./components/linear/LinearTemplateCard.vue')['default']
|
LinearTemplateCard: typeof import('./components/linear/LinearTemplateCard.vue')['default']
|
||||||
LinearTemplateSelector: typeof import('./components/linear/LinearTemplateSelector.vue')['default']
|
LinearTemplateSelector: typeof import('./components/linear/LinearTemplateSelector.vue')['default']
|
||||||
LinearTopBar: typeof import('./components/linear/LinearTopBar.vue')['default']
|
|
||||||
LinearTopNavbar: typeof import('./components/linear/LinearTopNavbar.vue')['default']
|
|
||||||
LinearWorkflowSidebar: typeof import('./components/linear/LinearWorkflowSidebar.vue')['default']
|
LinearWorkflowSidebar: typeof import('./components/linear/LinearWorkflowSidebar.vue')['default']
|
||||||
LinearWorkspace: typeof import('./components/linear/LinearWorkspace.vue')['default']
|
LinearWorkspace: typeof import('./components/linear/LinearWorkspace.vue')['default']
|
||||||
ModelsTab: typeof import('./components/v2/workspace/ModelsTab.vue')['default']
|
ModelsTab: typeof import('./components/v2/workspace/ModelsTab.vue')['default']
|
||||||
|
|||||||
@@ -1,234 +1,215 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useLinearModeStore } from '@/stores/linearModeStore'
|
import { useLinearModeStore } from '@/stores/linearModeStore'
|
||||||
|
import type { LinearOutput } from '@/types/linear'
|
||||||
|
|
||||||
const store = useLinearModeStore()
|
const store = useLinearModeStore()
|
||||||
|
|
||||||
|
const activeTab = ref<'queue' | 'history'>('queue')
|
||||||
|
|
||||||
|
const outputs = computed(() => store.outputs)
|
||||||
const isGenerating = computed(() => store.isGenerating)
|
const isGenerating = computed(() => store.isGenerating)
|
||||||
const currentWorkflow = computed(() => store.currentWorkflow)
|
const currentWorkflow = computed(() => store.currentWorkflow)
|
||||||
|
|
||||||
// Mock batches for demo - each batch is a generation session with multiple outputs
|
// Mock queue items
|
||||||
const batches = ref([
|
const queueItems = computed(() => {
|
||||||
{
|
if (!isGenerating.value || !currentWorkflow.value) return []
|
||||||
id: 'batch-1',
|
|
||||||
prompt: 'A mystical forest with glowing mushrooms and fairy lights, cinematic lighting, 8k',
|
|
||||||
model: 'Gen-4 Turbo',
|
|
||||||
duration: '5s',
|
|
||||||
createdAt: '2 min ago',
|
|
||||||
settings: { seed: 123456, steps: 30, cfg: 7.5 },
|
|
||||||
outputs: [
|
|
||||||
{ id: '1a', url: 'https://picsum.photos/seed/forest1/400/400', type: 'image' },
|
|
||||||
{ id: '1b', url: 'https://picsum.photos/seed/forest2/400/400', type: 'image' },
|
|
||||||
{ id: '1c', url: 'https://picsum.photos/seed/forest3/400/400', type: 'video' },
|
|
||||||
{ id: '1d', url: 'https://picsum.photos/seed/forest4/400/400', type: 'image' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'batch-2',
|
|
||||||
prompt: 'Cyberpunk city at night with neon lights and rain reflections',
|
|
||||||
model: 'Gen-4',
|
|
||||||
duration: '10s',
|
|
||||||
createdAt: '15 min ago',
|
|
||||||
settings: { seed: 789012, steps: 25, cfg: 8 },
|
|
||||||
outputs: [
|
|
||||||
{ id: '2a', url: 'https://picsum.photos/seed/cyber1/400/400', type: 'video' },
|
|
||||||
{ id: '2b', url: 'https://picsum.photos/seed/cyber2/400/400', type: 'image' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'batch-3',
|
|
||||||
prompt: 'Portrait of a woman with dramatic lighting, studio photography',
|
|
||||||
model: 'Gen-4 Turbo',
|
|
||||||
duration: '5s',
|
|
||||||
createdAt: '1 hour ago',
|
|
||||||
settings: { seed: 345678, steps: 30, cfg: 7 },
|
|
||||||
outputs: [
|
|
||||||
{ id: '3a', url: 'https://picsum.photos/seed/portrait1/400/400', type: 'image' },
|
|
||||||
{ id: '3b', url: 'https://picsum.photos/seed/portrait2/400/400', type: 'image' },
|
|
||||||
{ id: '3c', url: 'https://picsum.photos/seed/portrait3/400/400', type: 'image' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'batch-4',
|
|
||||||
prompt: 'Abstract fluid art in blue and gold, macro photography',
|
|
||||||
model: 'Flash 2.5',
|
|
||||||
duration: '5s',
|
|
||||||
createdAt: '3 hours ago',
|
|
||||||
settings: { seed: 901234, steps: 20, cfg: 6.5 },
|
|
||||||
outputs: [
|
|
||||||
{ id: '4a', url: 'https://picsum.photos/seed/abstract1/400/400', type: 'image' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
// Current generation progress
|
return [
|
||||||
const queueItem = computed(() => {
|
{
|
||||||
if (!isGenerating.value || !currentWorkflow.value) return null
|
id: currentWorkflow.value.id,
|
||||||
|
name: currentWorkflow.value.templateName,
|
||||||
return {
|
status: 'running' as const,
|
||||||
id: currentWorkflow.value.id,
|
progress: store.executionProgress,
|
||||||
name: currentWorkflow.value.templateName,
|
currentStep: currentWorkflow.value.currentStepIndex + 1,
|
||||||
progress: store.executionProgress,
|
totalSteps: currentWorkflow.value.steps.length,
|
||||||
}
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
function copyPrompt(prompt: string): void {
|
function formatTime(date: Date): string {
|
||||||
navigator.clipboard.writeText(prompt)
|
return new Intl.DateTimeFormat('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}).format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteBatch(batchId: string): void {
|
function handleDownload(output: LinearOutput): void {
|
||||||
const index = batches.value.findIndex(b => b.id === batchId)
|
const link = document.createElement('a')
|
||||||
if (index > -1) {
|
link.href = output.url
|
||||||
batches.value.splice(index, 1)
|
link.download = output.filename
|
||||||
}
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAll(batchId: string): void {
|
function handleDelete(outputId: string): void {
|
||||||
console.log('Download all from batch:', batchId)
|
store.deleteOutput(outputId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function reuseSettings(batchId: string): void {
|
function handleClearHistory(): void {
|
||||||
console.log('Reuse settings from batch:', batchId)
|
store.clearOutputs()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- Main content area - Batch gallery of creations -->
|
<!-- Main content area - takes remaining space -->
|
||||||
<main class="flex h-full flex-1 flex-col bg-zinc-950">
|
<main class="flex h-full flex-1 flex-col bg-zinc-950">
|
||||||
<div class="flex-1 overflow-y-auto">
|
<!-- Tabs -->
|
||||||
<!-- Currently Generating Batch -->
|
<div class="flex border-b border-zinc-800">
|
||||||
<div v-if="queueItem" class="border-b border-zinc-800 p-6">
|
<button
|
||||||
<div class="mb-4 flex items-center gap-3">
|
:class="[
|
||||||
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600">
|
'flex-1 px-4 py-2.5 text-xs font-medium transition-colors',
|
||||||
<i class="pi pi-spin pi-spinner text-sm text-white" />
|
activeTab === 'queue'
|
||||||
|
? 'border-b-2 border-blue-600 text-zinc-100'
|
||||||
|
: 'text-zinc-500 hover:text-zinc-300'
|
||||||
|
]"
|
||||||
|
@click="activeTab = 'queue'"
|
||||||
|
>
|
||||||
|
Queue
|
||||||
|
<span
|
||||||
|
v-if="queueItems.length"
|
||||||
|
class="ml-1.5 rounded-full bg-blue-600 px-1.5 py-0.5 text-[10px] text-white"
|
||||||
|
>
|
||||||
|
{{ queueItems.length }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="[
|
||||||
|
'flex-1 px-4 py-2.5 text-xs font-medium transition-colors',
|
||||||
|
activeTab === 'history'
|
||||||
|
? 'border-b-2 border-blue-600 text-zinc-100'
|
||||||
|
: 'text-zinc-500 hover:text-zinc-300'
|
||||||
|
]"
|
||||||
|
@click="activeTab = 'history'"
|
||||||
|
>
|
||||||
|
History
|
||||||
|
<span
|
||||||
|
v-if="outputs.length"
|
||||||
|
class="ml-1.5 rounded bg-zinc-700 px-1.5 py-0.5 text-[10px] text-zinc-400"
|
||||||
|
>
|
||||||
|
{{ outputs.length }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Queue View -->
|
||||||
|
<div v-if="activeTab === 'queue'" class="flex-1 overflow-y-auto">
|
||||||
|
<!-- Active Queue Items -->
|
||||||
|
<div v-if="queueItems.length" class="p-3">
|
||||||
|
<div
|
||||||
|
v-for="item in queueItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="rounded-lg border border-zinc-800 bg-zinc-800/50 p-3"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="h-2 w-2 animate-pulse rounded-full bg-blue-500" />
|
||||||
|
<span class="text-xs font-medium text-zinc-200">{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[10px] text-zinc-500">
|
||||||
|
Step {{ item.currentStep }}/{{ item.totalSteps }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
|
||||||
<div class="text-sm font-medium text-zinc-200">Generating...</div>
|
<!-- Progress -->
|
||||||
<div class="text-xs text-zinc-500">{{ Math.round(queueItem.progress) }}% complete</div>
|
<div class="mt-2">
|
||||||
|
<div class="h-1 overflow-hidden rounded-full bg-zinc-700">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full bg-blue-600 transition-all duration-300"
|
||||||
|
:style="{ width: `${item.progress}%` }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-right text-[10px] text-zinc-500">
|
||||||
|
{{ Math.round(item.progress) }}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-1.5 overflow-hidden rounded-full bg-zinc-800">
|
|
||||||
<div
|
|
||||||
class="h-full rounded-full bg-blue-500 transition-all duration-300"
|
|
||||||
:style="{ width: `${queueItem.progress}%` }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Batch Sections -->
|
<!-- Empty Queue -->
|
||||||
<div
|
<div
|
||||||
v-for="batch in batches"
|
v-else
|
||||||
:key="batch.id"
|
class="flex flex-col items-center justify-center py-12 text-zinc-500"
|
||||||
class="border-b border-zinc-800 p-6"
|
|
||||||
>
|
>
|
||||||
<!-- Batch Header -->
|
<i class="pi pi-clock mb-2 text-2xl" />
|
||||||
<div class="mb-4 flex items-start justify-between gap-4">
|
<span class="text-xs">Queue is empty</span>
|
||||||
<div class="min-w-0 flex-1">
|
<p class="mt-1 text-center text-[10px] text-zinc-600">
|
||||||
<!-- Prompt -->
|
Generated images will appear here
|
||||||
<p class="text-sm leading-relaxed text-zinc-200">
|
</p>
|
||||||
{{ batch.prompt }}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<!-- Meta Info -->
|
|
||||||
<div class="mt-2 flex flex-wrap items-center gap-3 text-[11px] text-zinc-500">
|
|
||||||
<span class="flex items-center gap-1">
|
|
||||||
<i class="pi pi-box text-[10px]" />
|
|
||||||
{{ batch.model }}
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center gap-1">
|
|
||||||
<i class="pi pi-clock text-[10px]" />
|
|
||||||
{{ batch.duration }}
|
|
||||||
</span>
|
|
||||||
<span class="flex items-center gap-1">
|
|
||||||
<i class="pi pi-history text-[10px]" />
|
|
||||||
{{ batch.createdAt }}
|
|
||||||
</span>
|
|
||||||
<span class="text-zinc-600">•</span>
|
|
||||||
<span class="text-zinc-600">
|
|
||||||
Seed: {{ batch.settings.seed }} · Steps: {{ batch.settings.steps }} · CFG: {{ batch.settings.cfg }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- History View -->
|
||||||
<div class="flex shrink-0 items-center gap-1">
|
<div v-else class="flex flex-1 flex-col overflow-hidden">
|
||||||
<button
|
<!-- History Header -->
|
||||||
v-tooltip.bottom="'Copy prompt'"
|
<div
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition-colors hover:bg-zinc-800 hover:text-zinc-200"
|
v-if="outputs.length"
|
||||||
@click="copyPrompt(batch.prompt)"
|
class="flex items-center justify-between border-b border-zinc-800 px-3 py-2"
|
||||||
>
|
>
|
||||||
<i class="pi pi-copy text-sm" />
|
<span class="text-[10px] text-zinc-500">{{ outputs.length }} generations</span>
|
||||||
</button>
|
<button
|
||||||
<button
|
class="text-[10px] text-zinc-500 transition-colors hover:text-red-400"
|
||||||
v-tooltip.bottom="'Reuse settings'"
|
@click="handleClearHistory"
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition-colors hover:bg-zinc-800 hover:text-zinc-200"
|
>
|
||||||
@click="reuseSettings(batch.id)"
|
Clear all
|
||||||
>
|
</button>
|
||||||
<i class="pi pi-replay text-sm" />
|
</div>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-tooltip.bottom="'Download all'"
|
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition-colors hover:bg-zinc-800 hover:text-zinc-200"
|
|
||||||
@click="downloadAll(batch.id)"
|
|
||||||
>
|
|
||||||
<i class="pi pi-download text-sm" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-tooltip.bottom="'Delete batch'"
|
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition-colors hover:bg-zinc-800 hover:text-red-400"
|
|
||||||
@click="deleteBatch(batch.id)"
|
|
||||||
>
|
|
||||||
<i class="pi pi-trash text-sm" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Outputs Grid -->
|
<!-- History Grid -->
|
||||||
<div class="flex flex-wrap gap-4">
|
<div v-if="outputs.length" class="flex-1 overflow-y-auto p-4">
|
||||||
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
|
||||||
<div
|
<div
|
||||||
v-for="output in batch.outputs"
|
v-for="output in outputs"
|
||||||
:key="output.id"
|
:key="output.id"
|
||||||
class="group relative h-48 w-48 cursor-pointer overflow-hidden rounded-xl bg-zinc-900 transition-all hover:ring-2 hover:ring-blue-500/50"
|
class="group relative aspect-square overflow-hidden rounded-lg bg-zinc-800"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="output.url"
|
:src="output.thumbnailUrl ?? output.url"
|
||||||
:alt="batch.prompt"
|
:alt="output.filename"
|
||||||
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
class="h-full w-full object-cover transition-transform group-hover:scale-105"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Video indicator -->
|
|
||||||
<div
|
|
||||||
v-if="output.type === 'video'"
|
|
||||||
class="absolute left-2 top-2 flex h-5 w-5 items-center justify-center rounded-full bg-black/70"
|
|
||||||
>
|
|
||||||
<i class="pi pi-play text-[8px] text-white" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hover Overlay -->
|
<!-- Hover Overlay -->
|
||||||
<div class="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
|
<div
|
||||||
<div class="flex gap-1">
|
class="absolute inset-0 flex flex-col justify-between bg-gradient-to-t from-black/80 via-transparent to-black/40 p-2 opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
<button class="flex h-7 w-7 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm transition-colors hover:bg-white/30">
|
>
|
||||||
<i class="pi pi-eye text-xs" />
|
<div class="flex justify-end">
|
||||||
</button>
|
<button
|
||||||
<button class="flex h-7 w-7 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm transition-colors hover:bg-white/30">
|
class="flex h-6 w-6 items-center justify-center rounded bg-black/50 text-zinc-300 transition-colors hover:bg-red-600 hover:text-white"
|
||||||
<i class="pi pi-download text-xs" />
|
@click="handleDelete(output.id)"
|
||||||
|
>
|
||||||
|
<i class="pi pi-trash text-[10px]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-[10px] text-zinc-300">
|
||||||
|
{{ formatTime(output.createdAt) }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="flex h-6 w-6 items-center justify-center rounded bg-black/50 text-zinc-300 transition-colors hover:bg-blue-600 hover:text-white"
|
||||||
|
@click="handleDownload(output)"
|
||||||
|
>
|
||||||
|
<i class="pi pi-download text-[10px]" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty History -->
|
||||||
<div
|
<div
|
||||||
v-if="batches.length === 0 && !queueItem"
|
v-else
|
||||||
class="flex h-full flex-col items-center justify-center p-12 text-zinc-500"
|
class="flex flex-1 flex-col items-center justify-center text-zinc-500"
|
||||||
>
|
>
|
||||||
<div class="flex h-20 w-20 items-center justify-center rounded-2xl bg-zinc-900">
|
<i class="pi pi-images mb-2 text-2xl" />
|
||||||
<i class="pi pi-images text-3xl" />
|
<span class="text-xs">No history yet</span>
|
||||||
</div>
|
<p class="mt-1 text-center text-[10px] text-zinc-600">
|
||||||
<h3 class="mt-4 text-sm font-medium text-zinc-300">No creations yet</h3>
|
Your creations will appear here
|
||||||
<p class="mt-1 text-center text-xs text-zinc-600">
|
|
||||||
Your generated images and videos will appear here
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ function selectTab(tab: LinearTab): void {
|
|||||||
@click="selectTab(tab.id)"
|
@click="selectTab(tab.id)"
|
||||||
>
|
>
|
||||||
<i :class="['pi', tab.icon, 'text-base']" />
|
<i :class="['pi', tab.icon, 'text-base']" />
|
||||||
|
<span class="mt-0.5 text-[8px] font-medium uppercase tracking-wide">{{ tab.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// Linear Mode Components - Runway-style 2-Column Layout
|
// Linear Mode Components - Runway-style 2-Column Layout
|
||||||
export { default as LinearTopBar } from './LinearTopBar.vue'
|
|
||||||
export { default as LinearIconSidebar } from './LinearIconSidebar.vue'
|
export { default as LinearIconSidebar } from './LinearIconSidebar.vue'
|
||||||
export { default as LinearCreationPanel } from './LinearCreationPanel.vue'
|
export { default as LinearCreationPanel } from './LinearCreationPanel.vue'
|
||||||
export { default as LinearHistoryPanel } from './LinearHistoryPanel.vue'
|
export { default as LinearHistoryPanel } from './LinearHistoryPanel.vue'
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ const router = useRouter()
|
|||||||
const isTeam = computed(() => props.workspaceId === 'team')
|
const isTeam = computed(() => props.workspaceId === 'team')
|
||||||
|
|
||||||
const userMenuGroups = computed<MenuGroup[]>(() => [
|
const userMenuGroups = computed<MenuGroup[]>(() => [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
items: [
|
||||||
|
{ label: 'Linear Mode', icon: 'pi pi-bolt', route: `/${props.workspaceId}/create` }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Overview',
|
label: 'Overview',
|
||||||
items: [
|
items: [
|
||||||
@@ -48,6 +54,12 @@ const userMenuGroups = computed<MenuGroup[]>(() => [
|
|||||||
])
|
])
|
||||||
|
|
||||||
const teamMenuGroups = computed<MenuGroup[]>(() => [
|
const teamMenuGroups = computed<MenuGroup[]>(() => [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
items: [
|
||||||
|
{ label: 'Linear Mode', icon: 'pi pi-bolt', route: `/${props.workspaceId}/create` }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Overview',
|
label: 'Overview',
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@@ -1,24 +1,51 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
import LinearIconSidebar from '@/components/linear/LinearIconSidebar.vue'
|
import LinearIconSidebar from '@/components/linear/LinearIconSidebar.vue'
|
||||||
import LinearCreationPanel from '@/components/linear/LinearCreationPanel.vue'
|
import LinearCreationPanel from '@/components/linear/LinearCreationPanel.vue'
|
||||||
import LinearHistoryPanel from '@/components/linear/LinearHistoryPanel.vue'
|
import LinearHistoryPanel from '@/components/linear/LinearHistoryPanel.vue'
|
||||||
import LinearTopBar from '@/components/linear/LinearTopBar.vue'
|
|
||||||
|
const sessionName = ref('Untitled session')
|
||||||
|
const credits = ref(4625)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="linear-view flex h-screen flex-col bg-zinc-950">
|
<div class="linear-view flex h-screen bg-zinc-950">
|
||||||
<!-- Top Bar with Logo, Home, Session Name, Credits -->
|
<!-- Left Icon Sidebar (Chat, Tool, Apps, Workflow) -->
|
||||||
<LinearTopBar />
|
<LinearIconSidebar />
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Left Creation Panel (prompt, upload, settings, generate) -->
|
||||||
<div class="flex flex-1 overflow-hidden">
|
<LinearCreationPanel />
|
||||||
<!-- Left Icon Sidebar (Chat, Tool, Apps, Workflow) -->
|
|
||||||
<LinearIconSidebar />
|
|
||||||
|
|
||||||
<!-- Left Creation Panel (prompt, upload, settings, generate) -->
|
<!-- Right Main Area (queue/history) -->
|
||||||
<LinearCreationPanel />
|
<div class="flex flex-1 flex-col">
|
||||||
|
<!-- Top Bar -->
|
||||||
|
<header class="flex h-12 shrink-0 items-center justify-between border-b border-zinc-800 bg-zinc-950 px-4">
|
||||||
|
<div />
|
||||||
|
|
||||||
<!-- Right Main Area (queue/history) -->
|
<!-- Center: Session Name -->
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="text-sm text-zinc-300">{{ sessionName }}</span>
|
||||||
|
<button class="p-1 text-zinc-500 transition-colors hover:text-zinc-300">
|
||||||
|
<i class="pi pi-ellipsis-h text-xs" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Credits + Upgrade -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button class="text-zinc-500 transition-colors hover:text-zinc-300">
|
||||||
|
<i class="pi pi-question-circle text-sm" />
|
||||||
|
</button>
|
||||||
|
<button class="text-zinc-500 transition-colors hover:text-zinc-300">
|
||||||
|
<i class="pi pi-external-link text-sm" />
|
||||||
|
</button>
|
||||||
|
<span class="text-xs text-zinc-400">{{ credits.toLocaleString() }} credits</span>
|
||||||
|
<button class="rounded-md bg-blue-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-blue-500">
|
||||||
|
Upgrade
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content Area -->
|
||||||
<LinearHistoryPanel />
|
<LinearHistoryPanel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user