mirror of
https://github.com/docker/docs.git
synced 2026-03-27 06:18:55 +07:00
site(gordon): add turn limit (10 messages)
Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
This commit is contained in:
@@ -8,13 +8,15 @@
|
||||
'https://ai-backend-service-stage.docker.com'
|
||||
{{- end }};
|
||||
</script>
|
||||
<div x-data="{
|
||||
<div
|
||||
x-data="{
|
||||
isLoading: false,
|
||||
error: null,
|
||||
messages: $persist([]).using(sessionStorage).as('gordon-messages'),
|
||||
currentQuestion: '',
|
||||
threadId: $persist(null).using(sessionStorage).as('gordon-threadId'),
|
||||
includePageContext: $persist(true).using(sessionStorage).as('gordon-includePageContext'),
|
||||
maxTurnsPerThread: 10,
|
||||
|
||||
init() {
|
||||
// Clean up any streaming messages that might be persisted
|
||||
@@ -43,9 +45,26 @@
|
||||
})
|
||||
},
|
||||
|
||||
getTurnCount() {
|
||||
return this.messages.filter(m => m.role === 'user').length
|
||||
},
|
||||
|
||||
getRemainingTurns() {
|
||||
return this.maxTurnsPerThread - this.getTurnCount()
|
||||
},
|
||||
|
||||
isThreadLimitReached() {
|
||||
return this.getTurnCount() >= this.maxTurnsPerThread
|
||||
},
|
||||
|
||||
shouldShowCountdown() {
|
||||
const remaining = this.getRemainingTurns()
|
||||
return remaining > 0 && remaining <= 3
|
||||
},
|
||||
|
||||
async askQuestion() {
|
||||
const question = this.currentQuestion.trim()
|
||||
if (!question || this.isLoading) {
|
||||
if (!question || this.isLoading || this.isThreadLimitReached()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -287,281 +306,393 @@
|
||||
console.error('Failed to copy:', err)
|
||||
}
|
||||
}
|
||||
}" x-cloak @keydown.escape.window="$store.gordon.close()">
|
||||
<!-- Overlay backdrop -->
|
||||
<div x-show="$store.gordon.isOpen" x-transition.opacity.duration.300ms @click="$store.gordon.close()"
|
||||
class="fixed inset-0 z-40 bg-black/50"></div>
|
||||
}"
|
||||
x-cloak
|
||||
@keydown.escape.window="$store.gordon.close()"
|
||||
>
|
||||
<!-- Overlay backdrop -->
|
||||
<div
|
||||
x-show="$store.gordon.isOpen"
|
||||
x-transition.opacity.duration.300ms
|
||||
@click="$store.gordon.close()"
|
||||
class="fixed inset-0 z-40 bg-black/50"
|
||||
></div>
|
||||
|
||||
<!-- Chat panel sliding in from right -->
|
||||
<div id="gordon-chat" x-show="$store.gordon.isOpen" x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0"
|
||||
x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-x-0"
|
||||
x-transition:leave-end="translate-x-full"
|
||||
class="fixed top-0 right-0 z-50 flex h-screen w-full flex-col overflow-hidden rounded-lg bg-white shadow-2xl transition-all duration-200 md:w-[min(80ch,90vw)] md:h-[calc(100vh-1rem)] md:top-2 md:right-2 dark:bg-gray-900">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex items-center justify-between rounded-t-lg border-b border-gray-200 bg-blue-600 px-6 py-3 dark:border-gray-700">
|
||||
<div class="flex items-center gap-3">
|
||||
{{ partial "utils/svg.html" "images/ask-ai-logo.svg" }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="clearChat()" title="Clear chat"
|
||||
class="cursor-pointer rounded p-2 text-white/80 transition-colors hover:bg-blue-500 hover:text-white"
|
||||
:disabled="messages.length === 0"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': messages.length === 0 }">
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "refresh" "refresh" }}
|
||||
</span>
|
||||
</button>
|
||||
<button @click="$store.gordon.close()"
|
||||
class="cursor-pointer rounded p-2 text-white/80 transition-colors hover:bg-blue-500 hover:text-white"
|
||||
title="Close chat" aria-label="Close chat">
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "close" "close" }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Chat panel sliding in from right -->
|
||||
<div
|
||||
id="gordon-chat"
|
||||
x-show="$store.gordon.isOpen"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="translate-x-full"
|
||||
x-transition:enter-end="translate-x-0"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="translate-x-0"
|
||||
x-transition:leave-end="translate-x-full"
|
||||
class="fixed top-0 right-0 z-50 flex h-screen w-full flex-col overflow-hidden rounded-lg bg-white shadow-2xl transition-all duration-200 md:top-2 md:right-2 md:h-[calc(100vh-1rem)] md:w-[min(80ch,90vw)] dark:bg-gray-900"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex items-center justify-between rounded-t-lg border-b border-gray-200 bg-blue-600 px-6 py-3 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
{{ partial "utils/svg.html" "images/ask-ai-logo.svg" }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="clearChat()"
|
||||
title="Clear chat"
|
||||
class="cursor-pointer rounded p-2 text-white/80 transition-colors hover:bg-blue-500 hover:text-white"
|
||||
:disabled="messages.length === 0"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': messages.length === 0 }"
|
||||
>
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "refresh" "refresh" }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
@click="$store.gordon.close()"
|
||||
class="cursor-pointer rounded p-2 text-white/80 transition-colors hover:bg-blue-500 hover:text-white"
|
||||
title="Close chat"
|
||||
aria-label="Close chat"
|
||||
>
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "close" "close" }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages container -->
|
||||
<div x-ref="messagesContainer" class="flex-1 space-y-4 p-6" :class="{ 'overflow-y-auto': messages.length > 0 }">
|
||||
<!-- Welcome message when empty -->
|
||||
<template x-if="messages.length === 0">
|
||||
<div class="flex h-full flex-col items-center justify-center text-center">
|
||||
<div class="mb-4 rounded-full bg-blue-100 p-4 dark:bg-blue-900">
|
||||
<span class="icon-svg text-blue-600 dark:text-blue-400">
|
||||
{{ partialCached "icon" "icons/sparkle.svg" "icons/sparkle.svg" }}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="mb-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Ask me about Docker
|
||||
</h3>
|
||||
<p class="max-w-sm text-gray-600 dark:text-gray-400">
|
||||
Get instant answers to your Docker questions. I can help with
|
||||
commands, concepts, troubleshooting, and best practices.
|
||||
</p>
|
||||
<div class="mt-8 flex flex-col items-center gap-3 text-sm">
|
||||
<p class="mb-1 text-gray-500 dark:text-gray-400">Try asking:</p>
|
||||
<button @click="currentQuestion = 'What is the Docker MCP toolkit?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
What is the Docker MCP toolkit?
|
||||
</button>
|
||||
<button @click="currentQuestion = 'How do hardened Docker images work?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
How do hardened Docker images work?
|
||||
</button>
|
||||
<button @click="currentQuestion = 'How do I use Docker Debug?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
How do I use Docker Debug?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Messages container -->
|
||||
<div
|
||||
x-ref="messagesContainer"
|
||||
class="flex-1 space-y-4 p-6"
|
||||
:class="{ 'overflow-y-auto': messages.length > 0 }"
|
||||
>
|
||||
<!-- Welcome message when empty -->
|
||||
<template x-if="messages.length === 0">
|
||||
<div
|
||||
class="flex h-full flex-col items-center justify-center text-center"
|
||||
>
|
||||
<div class="mb-4 rounded-full bg-blue-100 p-4 dark:bg-blue-900">
|
||||
<span class="icon-svg text-blue-600 dark:text-blue-400">
|
||||
{{ partialCached "icon" "icons/sparkle.svg" "icons/sparkle.svg" }}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="mb-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Ask me about Docker
|
||||
</h3>
|
||||
<p class="max-w-sm text-gray-600 dark:text-gray-400">
|
||||
Get instant answers to your Docker questions. I can help with
|
||||
commands, concepts, troubleshooting, and best practices.
|
||||
</p>
|
||||
<div class="mt-8 flex flex-col items-center gap-3 text-sm">
|
||||
<p class="mb-1 text-gray-500 dark:text-gray-400">Try asking:</p>
|
||||
<button
|
||||
@click="currentQuestion = 'What is the Docker MCP toolkit?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
What is the Docker MCP toolkit?
|
||||
</button>
|
||||
<button
|
||||
@click="currentQuestion = 'How do hardened Docker images work?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
How do hardened Docker images work?
|
||||
</button>
|
||||
<button
|
||||
@click="currentQuestion = 'How do I use Docker Debug?'; askQuestion()"
|
||||
class="cursor-pointer rounded-lg border border-gray-200 bg-gray-50 px-4 py-2.5 text-gray-700 transition-opacity hover:opacity-70 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
How do I use Docker Debug?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Messages -->
|
||||
<template x-for="(message, index) in messages" :key="index">
|
||||
<div :class="message.role === 'user' ? 'flex justify-end' : 'flex justify-start'">
|
||||
<div class="flex flex-col gap-2 max-w-full">
|
||||
<div :class="message.role === 'user' ? 'bg-blue-500 dark:bg-blue-800 text-white' : 'max-w-none bg-gray-100 dark:bg-gray-800'" class="prose prose-sm dark:prose-invert rounded-lg px-4">
|
||||
<template x-if="!message.content && message.isStreaming">
|
||||
<div class="flex gap-1 py-3">
|
||||
<span class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 0ms"></span>
|
||||
<span class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 150ms"></span>
|
||||
<span class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 300ms"></span>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="message.content">
|
||||
<div x-html="formatMessageContent(message.content)"></div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Feedback buttons for assistant messages -->
|
||||
<template x-if="message.role === 'assistant' && !message.isStreaming">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Copy button -->
|
||||
<button @click="copyAnswer(index)"
|
||||
class="cursor-pointer rounded bg-gray-100 p-1.5 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
:title="message.copied ? 'Copied!' : 'Copy answer'">
|
||||
<span x-show="message.copied !== true" class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "content_copy" "content_copy" }}
|
||||
</span>
|
||||
<span x-show="message.copied === true" class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "check_circle" "check_circle" }}
|
||||
</span>
|
||||
</button>
|
||||
<!-- Thumbs up -->
|
||||
<button @click="submitFeedback(index, 'positive')"
|
||||
:class="message.feedback === 'positive'
|
||||
<!-- Messages -->
|
||||
<template x-for="(message, index) in messages" :key="index">
|
||||
<div
|
||||
:class="message.role === 'user' ? 'flex justify-end' : 'flex justify-start'"
|
||||
>
|
||||
<div class="flex max-w-full flex-col gap-2">
|
||||
<div
|
||||
:class="message.role === 'user' ? 'bg-blue-500 dark:bg-blue-800 text-white' : 'max-w-none bg-gray-100 dark:bg-gray-800'"
|
||||
class="prose prose-sm dark:prose-invert rounded-lg px-4"
|
||||
>
|
||||
<template x-if="!message.content && message.isStreaming">
|
||||
<div class="flex gap-1 py-3">
|
||||
<span
|
||||
class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 0ms"
|
||||
></span>
|
||||
<span
|
||||
class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 150ms"
|
||||
></span>
|
||||
<span
|
||||
class="inline-block h-2 w-2 animate-bounce rounded-full bg-current"
|
||||
style="animation-delay: 300ms"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="message.content">
|
||||
<div x-html="formatMessageContent(message.content)"></div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Feedback buttons for assistant messages -->
|
||||
<template
|
||||
x-if="message.role === 'assistant' && !message.isStreaming"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Copy button -->
|
||||
<button
|
||||
@click="copyAnswer(index)"
|
||||
class="cursor-pointer rounded bg-gray-100 p-1.5 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
:title="message.copied ? 'Copied!' : 'Copy answer'"
|
||||
>
|
||||
<span
|
||||
x-show="message.copied !== true"
|
||||
class="icon-svg icon-sm"
|
||||
>
|
||||
{{ partialCached "icon" "content_copy" "content_copy" }}
|
||||
</span>
|
||||
<span
|
||||
x-show="message.copied === true"
|
||||
class="icon-svg icon-sm"
|
||||
>
|
||||
{{ partialCached "icon" "check_circle" "check_circle" }}
|
||||
</span>
|
||||
</button>
|
||||
<!-- Thumbs up -->
|
||||
<button
|
||||
@click="submitFeedback(index, 'positive')"
|
||||
:class="message.feedback === 'positive'
|
||||
? 'bg-green-200 text-green-700 dark:bg-green-900/50 dark:text-green-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'"
|
||||
class="cursor-pointer rounded p-1.5"
|
||||
title="Helpful">
|
||||
<span class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "thumb_up" "thumb_up" }}
|
||||
</span>
|
||||
</button>
|
||||
<!-- Thumbs down -->
|
||||
<button @click="submitFeedback(index, 'negative')"
|
||||
:class="message.feedback === 'negative'
|
||||
class="cursor-pointer rounded p-1.5"
|
||||
title="Helpful"
|
||||
>
|
||||
<span class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "thumb_up" "thumb_up" }}
|
||||
</span>
|
||||
</button>
|
||||
<!-- Thumbs down -->
|
||||
<button
|
||||
@click="submitFeedback(index, 'negative')"
|
||||
:class="message.feedback === 'negative'
|
||||
? 'bg-red-200 text-red-700 dark:bg-red-900/50 dark:text-red-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'"
|
||||
class="cursor-pointer rounded p-1.5"
|
||||
title="Not helpful">
|
||||
<span class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "thumb_down" "thumb_down" }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<template x-if="message.feedback">
|
||||
<span :class="message.feedback === 'positive' ? 'text-green-600 dark:text-green-400' : 'text-gray-600 dark:text-gray-400'">
|
||||
Thanks for your feedback!
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="message.feedbackError">
|
||||
<span class="text-red-600 dark:text-red-400" x-text="message.feedbackError"></span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
class="cursor-pointer rounded p-1.5"
|
||||
title="Not helpful"
|
||||
>
|
||||
<span class="icon-svg icon-sm">
|
||||
{{ partialCached "icon" "thumb_down" "thumb_down" }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<template x-if="message.feedback">
|
||||
<span
|
||||
:class="message.feedback === 'positive' ? 'text-green-600 dark:text-green-400' : 'text-gray-600 dark:text-gray-400'"
|
||||
>
|
||||
Thanks for your feedback!
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="message.feedbackError">
|
||||
<span
|
||||
class="text-red-600 dark:text-red-400"
|
||||
x-text="message.feedbackError"
|
||||
></span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Error message -->
|
||||
<template x-if="error">
|
||||
<div
|
||||
class="rounded-lg border border-red-200 bg-red-50 p-4 text-red-800 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400">
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Error message -->
|
||||
<template x-if="error">
|
||||
<div
|
||||
class="rounded-lg border border-red-200 bg-red-50 p-4 text-red-800 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
|
||||
>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Input area -->
|
||||
<div class="border-t border-gray-200 p-4 dark:border-gray-700">
|
||||
<form @submit.prevent="askQuestion()" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1 self-stretch">
|
||||
<textarea x-ref="input" x-model="currentQuestion"
|
||||
@input="$el.style.height = 'auto'; $el.style.height = $el.scrollHeight + 'px'"
|
||||
@keydown.enter="if (!$event.shiftKey) { $event.preventDefault(); askQuestion() }"
|
||||
placeholder="Ask a question about Docker..."
|
||||
rows="1"
|
||||
:disabled="isLoading"
|
||||
class="block w-full resize-none rounded-lg border border-gray-300 p-3 leading-normal min-h-[3rem] focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-blue-400"></textarea>
|
||||
</div>
|
||||
<button type="submit" :disabled="!currentQuestion.trim() || isLoading"
|
||||
:title="isLoading ? 'Sending...' : 'Send question'"
|
||||
class="cursor-pointer rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50">
|
||||
<template x-if="!isLoading">
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "send" "send" }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="isLoading">
|
||||
<span class="icon-svg animate-spin">
|
||||
{{ partialCached "icon" "progress_activity" "progress_activity" }}
|
||||
</span>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center" x-data="{ showTooltip: false }">
|
||||
<div class="relative">
|
||||
<button
|
||||
@click="includePageContext = !includePageContext"
|
||||
@mouseenter="showTooltip = true"
|
||||
@mouseleave="showTooltip = false"
|
||||
type="button"
|
||||
:class="includePageContext
|
||||
<!-- Countdown warning when approaching limit -->
|
||||
<template x-if="shouldShowCountdown()">
|
||||
<div
|
||||
class="rounded-lg border border-yellow-200 bg-yellow-50 p-3 text-yellow-800 dark:border-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400"
|
||||
>
|
||||
<p class="text-sm">
|
||||
<span x-text="getRemainingTurns()"></span>
|
||||
<span
|
||||
x-text="getRemainingTurns() === 1 ? 'question' : 'questions'"
|
||||
></span>
|
||||
remaining in this thread.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Thread limit warning -->
|
||||
<template x-if="isThreadLimitReached()">
|
||||
<div
|
||||
class="rounded-lg border border-blue-200 bg-blue-50 p-4 text-blue-800 dark:border-blue-800 dark:bg-blue-900/20 dark:text-blue-400"
|
||||
>
|
||||
<p class="mb-3 text-sm">
|
||||
You've reached the maximum of
|
||||
<span x-text="maxTurnsPerThread"></span> questions per thread. For
|
||||
better answer quality, start a new thread.
|
||||
</p>
|
||||
<button
|
||||
@click="clearChat()"
|
||||
class="cursor-pointer rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
>
|
||||
Start a new thread
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Input area -->
|
||||
<div class="border-t border-gray-200 p-4 dark:border-gray-700">
|
||||
<form @submit.prevent="askQuestion()" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1 self-stretch">
|
||||
<textarea
|
||||
x-ref="input"
|
||||
x-model="currentQuestion"
|
||||
@input="$el.style.height = 'auto'; $el.style.height = $el.scrollHeight + 'px'"
|
||||
@keydown.enter="if (!$event.shiftKey) { $event.preventDefault(); askQuestion() }"
|
||||
placeholder="Ask a question about Docker..."
|
||||
rows="1"
|
||||
:disabled="isLoading || isThreadLimitReached()"
|
||||
class="block min-h-[3rem] w-full resize-none rounded-lg border border-gray-300 p-3 leading-normal focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:focus:border-blue-400"
|
||||
:class="{ 'cursor-not-allowed': isThreadLimitReached() }"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!currentQuestion.trim() || isLoading || isThreadLimitReached()"
|
||||
:title="isLoading ? 'Sending...' : (isThreadLimitReached() ? 'Thread limit reached. Start a new thread.' : 'Send question')"
|
||||
class="cursor-pointer rounded-lg bg-blue-600 px-4 py-3 font-semibold text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<template x-if="!isLoading">
|
||||
<span class="icon-svg">
|
||||
{{ partialCached "icon" "send" "send" }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="isLoading">
|
||||
<span class="icon-svg animate-spin">
|
||||
{{ partialCached "icon" "progress_activity" "progress_activity" }}
|
||||
</span>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center" x-data="{ showTooltip: false }">
|
||||
<div class="relative">
|
||||
<button
|
||||
@click="includePageContext = !includePageContext"
|
||||
@mouseenter="showTooltip = true"
|
||||
@mouseleave="showTooltip = false"
|
||||
type="button"
|
||||
:class="includePageContext
|
||||
? 'bg-blue-100 text-blue-600 dark:bg-blue-800 dark:text-blue-200'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400'"
|
||||
class="cursor-pointer rounded-md px-2 py-1 text-xs font-medium transition-colors hover:opacity-80">
|
||||
<span class="flex items-center gap-1">
|
||||
<template x-if="includePageContext">
|
||||
<span class="icon-svg icon-xs">
|
||||
{{ partialCached "icon" "link" "link" }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!includePageContext">
|
||||
<span class="icon-svg icon-xs">
|
||||
{{ partialCached "icon" "link_off" "link_off" }}
|
||||
</span>
|
||||
</template>
|
||||
<span>Context</span>
|
||||
</span>
|
||||
</button>
|
||||
<!-- Tooltip -->
|
||||
<div
|
||||
x-show="showTooltip"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute bottom-full left-0 mb-2 w-56 rounded-lg bg-gray-900 p-2.5 text-xs text-white shadow-lg dark:bg-gray-700"
|
||||
style="display: none;">
|
||||
<div class="relative">
|
||||
<p>When enabled, Gordon considers the current page you're viewing to provide more relevant answers.</p>
|
||||
<!-- Arrow -->
|
||||
<div class="absolute -bottom-3 left-4 h-2 w-2 rotate-45 bg-gray-900 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
class="cursor-pointer rounded-md px-2 py-1 text-xs font-medium transition-colors hover:opacity-80"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<template x-if="includePageContext">
|
||||
<span class="icon-svg icon-xs">
|
||||
{{ partialCached "icon" "link" "link" }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!includePageContext">
|
||||
<span class="icon-svg icon-xs">
|
||||
{{ partialCached "icon" "link_off" "link_off" }}
|
||||
</span>
|
||||
</template>
|
||||
<span>Context</span>
|
||||
</span>
|
||||
</button>
|
||||
<!-- Tooltip -->
|
||||
<div
|
||||
x-show="showTooltip"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute bottom-full left-0 mb-2 w-56 rounded-lg bg-gray-900 p-2.5 text-xs text-white shadow-lg dark:bg-gray-700"
|
||||
style="display: none;"
|
||||
>
|
||||
<div class="relative">
|
||||
<p>
|
||||
When enabled, Gordon considers the current page you're viewing
|
||||
to provide more relevant answers.
|
||||
</p>
|
||||
<!-- Arrow -->
|
||||
<div
|
||||
class="absolute -bottom-3 left-4 h-2 w-2 rotate-45 bg-gray-900 dark:bg-gray-700"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Disclaimer -->
|
||||
<div
|
||||
class="rounded-b-lg border-t border-gray-200 bg-blue-50 px-4 py-3 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400">
|
||||
This is a custom LLM for answering questions about Docker. Answers are
|
||||
based on the documentation.
|
||||
</div>
|
||||
</div>
|
||||
<!-- Disclaimer -->
|
||||
<div
|
||||
class="rounded-b-lg border-t border-gray-200 bg-blue-50 px-4 py-3 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400"
|
||||
>
|
||||
This is a custom LLM for answering questions about Docker. Answers are
|
||||
based on the documentation.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Styles for Gordon chat -->
|
||||
<style>
|
||||
/* Code block styles */
|
||||
#gordon-chat pre {
|
||||
background: #0d1117;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
/* Code block styles */
|
||||
#gordon-chat pre {
|
||||
background: #0d1117;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#gordon-chat pre code {
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
padding: 1rem;
|
||||
display: block;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
#gordon-chat pre code {
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
padding: 1rem;
|
||||
display: block;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#gordon-chat pre code * {
|
||||
white-space: pre;
|
||||
}
|
||||
#gordon-chat pre code * {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* Inline code styling (not in pre blocks) */
|
||||
#gordon-chat .prose code.not-prose {
|
||||
background-color: rgb(229 231 235);
|
||||
color: rgb(17 24 39);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
/* Inline code styling (not in pre blocks) */
|
||||
#gordon-chat .prose code.not-prose {
|
||||
background-color: rgb(229 231 235);
|
||||
color: rgb(17 24 39);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.dark #gordon-chat .prose code.not-prose {
|
||||
background-color: rgb(55 65 81);
|
||||
color: rgb(229 231 235);
|
||||
}
|
||||
.dark #gordon-chat .prose code.not-prose {
|
||||
background-color: rgb(55 65 81);
|
||||
color: rgb(229 231 235);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user