11<script setup lang="ts">
22import { computed , ref , watch } from ' vue'
3+ import { useDisplay } from ' vuetify'
34import { useModuleI18n } from ' @/i18n/composables'
45import { usePipelineSnapshot } from ' @/composables/usePipelineSnapshot'
56import type { EffectTarget , PipelineSnapshot , PipelineStageId , SnapshotScopeMode , StageParticipant } from ' ./pipelineSnapshotTypes'
@@ -14,6 +15,8 @@ import ConflictListPanel from './ConflictListPanel.vue'
1415import TraceImpactDetailPanel from ' ./TraceImpactDetailPanel.vue'
1516import PromptPreviewDialog from ' ./PromptPreviewDialog.vue'
1617
18+ import ResizableSplitPane from ' ../ResizableSplitPane.vue'
19+
1720type SnapshotPanelMode = ' pipeline' | ' trace'
1821type RightTab = ' detail' | ' conflicts'
1922
@@ -51,6 +54,9 @@ const props = withDefaults(
5154
5255const { tm } = useModuleI18n (' features/extension' )
5356
57+ const display = useDisplay ()
58+ const isSmallScreen = computed (() => display .width .value <= 960 )
59+
5460const scopeMode = ref <SnapshotScopeMode >(' global' )
5561const sessionUmo = ref <string >(' ' )
5662
@@ -60,6 +66,27 @@ const selectedParticipantId = ref<string | null>(null)
6066
6167const showAllStages = ref (false )
6268
69+ const SPLIT_RATIO_KEY = ' psp-split-ratio'
70+
71+ function parseStoredRatio(raw : string | null ): number | null {
72+ if (! raw ) return null
73+ const num = Number (raw )
74+ if (! Number .isFinite (num )) return null
75+ if (num <= 0 || num >= 1 ) return null
76+ return num
77+ }
78+
79+ const splitRatio = ref (parseStoredRatio (localStorage .getItem (SPLIT_RATIO_KEY )) ?? 0.67 )
80+
81+ watch (
82+ splitRatio ,
83+ (val ) => {
84+ if (! Number .isFinite (val )) return
85+ localStorage .setItem (SPLIT_RATIO_KEY , String (val ))
86+ },
87+ { flush: ' post' }
88+ )
89+
6390const traceSelectedTarget = ref <' __all__' | EffectTarget >(' __all__' )
6491const traceSelectedImpactKey = ref <string | null >(null )
6592
@@ -381,88 +408,179 @@ const scopeItems = computed(() => [
381408 <v-progress-linear v-if =" loading" indeterminate color =" primary" />
382409
383410 <div class =" psp__content d-flex flex-grow-1" style =" min-height : 0 " >
384- <div class =" psp__left" >
385- <TraceFishboneView
386- v-if =" props.mode === 'trace'"
387- class =" psp__fishbone"
388- :groups =" traceRootGroupsAll"
389- :active-stage-id =" selectedStageId"
390- :active-target =" traceActiveTarget"
391- :active-impact-key =" traceSelectedImpactKey"
392- @select-stage =" handleSelectStage"
393- @select-target =" (t) => (traceSelectedTarget = t)"
394- @select-impact =" selectImpactKey"
395- />
396- <PipelineFishboneView
397- v-else
398- class =" psp__fishbone"
399- :snapshot =" displaySnapshot"
400- :selected-stage-id =" selectedStageId"
401- :selected-participant-id =" selectedParticipantId"
402- :show-all-stages =" showAllStages"
403- @select-stage =" handleSelectStage"
404- @select-participant =" handleSelectParticipant"
405- />
406- </div >
407-
408- <div class =" psp__right" >
409- <div v-if =" props.mode === 'pipeline'" class =" psp__right-tabs" >
410- <v-btn-toggle v-model =" rightTab" mandatory density =" compact" class =" psp__right-toggle w-100" >
411- <v-btn value =" detail" variant =" text" class =" psp__right-toggle-btn flex-1" >
412- {{ tm('pipeline.rightTabs.detail') }}
413- </v-btn >
414- <v-btn value =" conflicts" variant =" text" class =" psp__right-toggle-btn flex-1" >
415- {{ tm('pipeline.rightTabs.conflicts') }}
416- </v-btn >
417- </v-btn-toggle >
418-
419- <v-divider />
411+ <template v-if =" isSmallScreen " >
412+ <div class =" psp__left" >
413+ <TraceFishboneView
414+ v-if =" props.mode === 'trace'"
415+ class =" psp__fishbone"
416+ :groups =" traceRootGroupsAll"
417+ :active-stage-id =" selectedStageId"
418+ :active-target =" traceActiveTarget"
419+ :active-impact-key =" traceSelectedImpactKey"
420+ @select-stage =" handleSelectStage"
421+ @select-target =" (t) => (traceSelectedTarget = t)"
422+ @select-impact =" selectImpactKey"
423+ />
424+ <PipelineFishboneView
425+ v-else
426+ class =" psp__fishbone"
427+ :snapshot =" displaySnapshot"
428+ :selected-stage-id =" selectedStageId"
429+ :selected-participant-id =" selectedParticipantId"
430+ :show-all-stages =" showAllStages"
431+ @select-stage =" handleSelectStage"
432+ @select-participant =" handleSelectParticipant"
433+ />
420434 </div >
421435
422- <div class =" psp__right-body" >
423- <v-window
424- v-if =" props.mode === 'pipeline'"
425- v-model =" rightTab"
426- class =" psp__window flex-grow-1"
427- style =" min-height : 0 "
428- >
429- <v-window-item value =" detail" class =" h-100" >
430- <StageDetailPanel
431- :snapshot =" displaySnapshot"
432- :stage-id =" selectedStageId"
433- :selected-participant-id =" selectedParticipantId"
434- @select-participant =" handleSelectParticipant"
435- @select-plugin =" handleSelectPlugin"
436- @view-impact-chain =" handleViewImpactChain"
437- />
438- </v-window-item >
436+ <div class =" psp__right" >
437+ <div v-if =" props.mode === 'pipeline'" class =" psp__right-tabs" >
438+ <v-btn-toggle v-model =" rightTab" mandatory density =" compact" class =" psp__right-toggle w-100" >
439+ <v-btn value =" detail" variant =" text" class =" psp__right-toggle-btn flex-1" >
440+ {{ tm('pipeline.rightTabs.detail') }}
441+ </v-btn >
442+ <v-btn value =" conflicts" variant =" text" class =" psp__right-toggle-btn flex-1" >
443+ {{ tm('pipeline.rightTabs.conflicts') }}
444+ </v-btn >
445+ </v-btn-toggle >
446+
447+ <v-divider />
448+ </div >
439449
440- <v-window-item value =" conflicts" class =" h-100" >
441- <ConflictListPanel
442- :snapshot =" displaySnapshot"
443- @select-stage =" handleSelectStage"
444- @select-participant =" handleSelectParticipant"
450+ <div class =" psp__right-body" >
451+ <v-window
452+ v-if =" props.mode === 'pipeline'"
453+ v-model =" rightTab"
454+ class =" psp__window flex-grow-1"
455+ style =" min-height : 0 "
456+ >
457+ <v-window-item value =" detail" class =" h-100" >
458+ <StageDetailPanel
459+ :snapshot =" displaySnapshot"
460+ :stage-id =" selectedStageId"
461+ :selected-participant-id =" selectedParticipantId"
462+ @select-participant =" handleSelectParticipant"
463+ @select-plugin =" handleSelectPlugin"
464+ @view-impact-chain =" handleViewImpactChain"
465+ />
466+ </v-window-item >
467+
468+ <v-window-item value =" conflicts" class =" h-100" >
469+ <ConflictListPanel
470+ :snapshot =" displaySnapshot"
471+ @select-stage =" handleSelectStage"
472+ @select-participant =" handleSelectParticipant"
473+ @select-plugin =" handleSelectPlugin"
474+ />
475+ </v-window-item >
476+ </v-window >
477+
478+ <div v-else class =" h-100 d-flex flex-column" style =" min-height : 0 " >
479+ <TraceImpactDetailPanel
480+ class =" flex-grow-1"
481+ :row =" traceSelectedRow"
445482 @select-plugin =" handleSelectPlugin"
483+ @navigate-pipeline =" handleNavigatePipeline"
446484 />
447- </v-window-item >
448- </v-window >
449-
450- <div v-else class =" h-100 d-flex flex-column" style =" min-height : 0 " >
451- <TraceImpactDetailPanel
452- class =" flex-grow-1"
453- :row =" traceSelectedRow"
454- @select-plugin =" handleSelectPlugin"
455- @navigate-pipeline =" handleNavigatePipeline"
485+ </div >
486+
487+ <div v-if =" error" class =" psp__error px-4 py-3" >
488+ <v-alert type =" error" variant =" tonal" density =" comfortable" >
489+ {{ error }}
490+ </v-alert >
491+ </div >
492+ </div >
493+ </div >
494+ </template >
495+
496+ <ResizableSplitPane v-else v-model =" splitRatio" direction =" horizontal" class =" psp__split" >
497+ <template #first >
498+ <div class =" psp__left" >
499+ <TraceFishboneView
500+ v-if =" props.mode === 'trace'"
501+ class =" psp__fishbone"
502+ :groups =" traceRootGroupsAll"
503+ :active-stage-id =" selectedStageId"
504+ :active-target =" traceActiveTarget"
505+ :active-impact-key =" traceSelectedImpactKey"
506+ @select-stage =" handleSelectStage"
507+ @select-target =" (t) => (traceSelectedTarget = t)"
508+ @select-impact =" selectImpactKey"
509+ />
510+ <PipelineFishboneView
511+ v-else
512+ class =" psp__fishbone"
513+ :snapshot =" displaySnapshot"
514+ :selected-stage-id =" selectedStageId"
515+ :selected-participant-id =" selectedParticipantId"
516+ :show-all-stages =" showAllStages"
517+ @select-stage =" handleSelectStage"
518+ @select-participant =" handleSelectParticipant"
456519 />
457520 </div >
521+ </template >
458522
459- <div v-if =" error" class =" psp__error px-4 py-3" >
460- <v-alert type =" error" variant =" tonal" density =" comfortable" >
461- {{ error }}
462- </v-alert >
523+ <template #second >
524+ <div class =" psp__right" >
525+ <div v-if =" props.mode === 'pipeline'" class =" psp__right-tabs" >
526+ <v-btn-toggle v-model =" rightTab" mandatory density =" compact" class =" psp__right-toggle w-100" >
527+ <v-btn value =" detail" variant =" text" class =" psp__right-toggle-btn flex-1" >
528+ {{ tm('pipeline.rightTabs.detail') }}
529+ </v-btn >
530+ <v-btn value =" conflicts" variant =" text" class =" psp__right-toggle-btn flex-1" >
531+ {{ tm('pipeline.rightTabs.conflicts') }}
532+ </v-btn >
533+ </v-btn-toggle >
534+
535+ <v-divider />
536+ </div >
537+
538+ <div class =" psp__right-body" >
539+ <v-window
540+ v-if =" props.mode === 'pipeline'"
541+ v-model =" rightTab"
542+ class =" psp__window flex-grow-1"
543+ style =" min-height : 0 "
544+ >
545+ <v-window-item value =" detail" class =" h-100" >
546+ <StageDetailPanel
547+ :snapshot =" displaySnapshot"
548+ :stage-id =" selectedStageId"
549+ :selected-participant-id =" selectedParticipantId"
550+ @select-participant =" handleSelectParticipant"
551+ @select-plugin =" handleSelectPlugin"
552+ @view-impact-chain =" handleViewImpactChain"
553+ />
554+ </v-window-item >
555+
556+ <v-window-item value =" conflicts" class =" h-100" >
557+ <ConflictListPanel
558+ :snapshot =" displaySnapshot"
559+ @select-stage =" handleSelectStage"
560+ @select-participant =" handleSelectParticipant"
561+ @select-plugin =" handleSelectPlugin"
562+ />
563+ </v-window-item >
564+ </v-window >
565+
566+ <div v-else class =" h-100 d-flex flex-column" style =" min-height : 0 " >
567+ <TraceImpactDetailPanel
568+ class =" flex-grow-1"
569+ :row =" traceSelectedRow"
570+ @select-plugin =" handleSelectPlugin"
571+ @navigate-pipeline =" handleNavigatePipeline"
572+ />
573+ </div >
574+
575+ <div v-if =" error" class =" psp__error px-4 py-3" >
576+ <v-alert type =" error" variant =" tonal" density =" comfortable" >
577+ {{ error }}
578+ </v-alert >
579+ </div >
580+ </div >
463581 </div >
464- </div >
465- </div >
582+ </template >
583+ </ResizableSplitPane >
466584 </div >
467585
468586 <PromptPreviewDialog v-model:show =" promptDialog" :preview =" displaySnapshot?.llm_prompt_preview ?? null" />
@@ -499,10 +617,13 @@ const scopeItems = computed(() => [
499617 min-width : 0 ;
500618}
501619
620+ .psp__split {
621+ flex : 1 1 auto ;
622+ min-height : 0 ;
623+ min-width : 0 ;
624+ }
625+
502626.psp__right {
503- width : clamp (360px , 42vw , 760px );
504- max-width : 60% ;
505- min-width : 360px ;
506627 min-height : 0 ;
507628 display : flex ;
508629 flex-direction : column ;
@@ -580,11 +701,5 @@ const scopeItems = computed(() => [
580701 border-right : 0 ;
581702 border-bottom : 1px solid rgba (var (--v-theme-on-surface ), 0.08 );
582703 }
583-
584- .psp__right {
585- width : 100% ;
586- max-width : 100% ;
587- min-width : 0 ;
588- }
589704}
590705 </style >
0 commit comments