@@ -9,6 +9,7 @@ import ArtifactMenu from "./components/ArtifactMenu.mjs"
99import ArtifactReactions from "./components/ArtifactReactions.mjs"
1010import RatingsBadge from "./components/RatingsBadge.mjs"
1111import AudioPlayer from "./components/AudioPlayer.mjs"
12+ import { renderMarkdown } from "../lib/mjs/markdown.mjs"
1213
1314export default {
1415 components : {
@@ -118,8 +119,14 @@ export default {
118119 </div>
119120 </div>
120121
121- <div v-if="selectedArtifact?.description" class="p-4 text-sm text-gray-600 dark:text-gray-400 whitespace-pre-wrap">
122- {{descriptionFilter(selectedArtifact.description)}}
122+ <div v-if="selectedArtifact?.description" id="description" class="max-h-76 overflow-hidden p-4 text-sm text-gray-600 dark:text-gray-400 prose">
123+ <div v-html="descriptionFilter(selectedArtifact?.description)"></div>
124+ </div>
125+ <div class="flex justify-center">
126+ <button type="button" data-more="#description" data-more-remove="max-h-76 overflow-hidden"
127+ class="hidden mt-2 mb-4 text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 text-sm font-medium">
128+ show more
129+ </button>
123130 </div>
124131
125132 <!-- Discussion Section -->
@@ -278,26 +285,41 @@ export default {
278285
279286 <!-- Prompt -->
280287 <div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
288+
289+ <div id="positivePrompt" class="max-h-92 overflow-hidden">
281290 <div class="flex items-center justify-between mb-4">
282- <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Prompt</h3>
283- <button @click="copyDescription" type="button"
284- class="p-1 rounded-md border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors opacity-50 hover:opacity-100"
285- :title="copiedDescription ? 'Copied!' : 'Copy description'">
286- <svg v-if="copiedDescription" class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
287- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
288- </svg>
289- <svg v-else class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
290- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path>
291- </svg>
292- </button>
291+ <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Prompt</h3>
292+ <button @click="copyDescription" type="button"
293+ class="p-1 rounded-md border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors opacity-50 hover:opacity-100"
294+ :title="copiedDescription ? 'Copied!' : 'Copy description'">
295+ <svg v-if="copiedDescription" class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
296+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
297+ </svg>
298+ <svg v-else class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
299+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path>
300+ </svg>
301+ </button>
293302 </div>
294303 <p class="text-gray-700 dark:text-gray-300 leading-relaxed text-sm">
295- {{ generation.args?.positivePrompt || generation.description || 'No description available' }}
304+ {{ generation.args?.positivePrompt || generation.description || 'No description available' }}
296305 </p>
297- <div v-if="generation.args?.negativePrompt" class="mt-4">
306+ </div>
307+
308+ <button type="button" data-more="#positivePrompt" data-more-remove="max-h-92 overflow-hidden"
309+ class="hidden text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 text-sm font-medium">
310+ more
311+ </button>
312+
313+ <div v-if="generation.args?.negativePrompt" id="negativePrompt" class="mt-4 max-h-68 overflow-hidden">
298314 <h4 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">Negative Prompt</h4>
299- <p class="text-sm text-gray-600 dark:text-gray-400">{{ generation.args.negativePrompt }}</p>
315+ <p class="text-sm text-gray-600 dark:text-gray-400" :title="generation.args.negativePrompt">
316+ {{ generation.args.negativePrompt }}
317+ </p>
300318 </div>
319+ <button type="button" data-more="#negativePrompt" data-more-remove="max-h-68 overflow-hidden"
320+ class="hidden text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 text-sm font-medium">
321+ more
322+ </button>
301323 </div>
302324
303325 <!-- Technical Details -->
@@ -446,6 +468,7 @@ export default {
446468 const loadingComments = ref ( false )
447469 const userVotes = ref ( { } )
448470 const copiedDescription = ref ( false )
471+ const showFullDescription = ref ( false )
449472 const workflow = computed ( ( ) => store . workflows . find ( x => x . id === generation . value ?. workflowId ) )
450473 const workflowVersion = computed ( ( ) =>
451474 store . workflowVersions . find ( x => x . id === generation . value ?. versionId )
@@ -606,6 +629,17 @@ export default {
606629 onMounted ( async ( ) => {
607630 document . addEventListener ( "keydown" , handleKeydown )
608631 await onRouteChange ( )
632+
633+ document . querySelectorAll ( '[data-more]' ) . forEach ( el => {
634+ const target = document . querySelector ( el . dataset . more )
635+ if ( target . scrollHeight > target . clientHeight ) {
636+ el . classList . remove ( 'hidden' )
637+ }
638+ el . addEventListener ( 'click' , ( ) => {
639+ el . dataset . moreRemove . split ( ' ' ) . forEach ( x => target . classList . remove ( x ) )
640+ el . classList . add ( 'hidden' )
641+ } )
642+ } )
609643 } )
610644
611645 onUnmounted ( ( ) => {
@@ -663,9 +697,32 @@ export default {
663697 : caption
664698 }
665699 function descriptionFilter ( description ) {
666- return description
700+ return renderMarkdown ( description )
667701 }
668702
703+ function toggleDescription ( ) {
704+ showFullDescription . value = ! showFullDescription . value
705+ }
706+
707+ const truncateAfter = 700
708+ const shouldTruncateDescription = computed ( ( ) => {
709+ if ( ! selectedArtifact . value ?. description ) return false
710+ return selectedArtifact . value . description . length > truncateAfter // When to truncate
711+ } )
712+
713+ const displayedDescription = computed ( ( ) => {
714+ if ( ! selectedArtifact . value ?. description ) return ''
715+ if ( ! shouldTruncateDescription . value || showFullDescription . value ) {
716+ return selectedArtifact . value . description
717+ }
718+ return selectedArtifact . value . description . substring ( 0 , truncateAfter ) + '...'
719+ } )
720+
721+ // Reset show more state when artifact changes
722+ watch ( selectedArtifact , ( ) => {
723+ showFullDescription . value = false
724+ } )
725+
669726 return {
670727 Q :$1 ,
671728 store,
@@ -706,6 +763,10 @@ export default {
706763 isPlaying,
707764 captionFilter,
708765 descriptionFilter,
766+ toggleDescription,
767+ showFullDescription,
768+ shouldTruncateDescription,
769+ displayedDescription,
709770 }
710771 }
711772}
0 commit comments