From 517fe7c1d3cd5a4be240e60dfb0085f8a089ded7 Mon Sep 17 00:00:00 2001 From: Sharad Regoti Date: Thu, 17 Apr 2025 10:52:22 +0530 Subject: [PATCH 01/16] Initial Commit --- .../themes/tykio/layouts/_default/baseof.html | 460 +++++++++++------- 1 file changed, 289 insertions(+), 171 deletions(-) diff --git a/tyk-docs/themes/tykio/layouts/_default/baseof.html b/tyk-docs/themes/tykio/layouts/_default/baseof.html index e02ea17505..7511306de4 100755 --- a/tyk-docs/themes/tykio/layouts/_default/baseof.html +++ b/tyk-docs/themes/tykio/layouts/_default/baseof.html @@ -1,191 +1,309 @@ - - - - - {{ .Title }} - - - - - {{ partial "google_search_console_metadata.html" . }} - {{ partial "site_schema.html" . }} - - {{ if eq .Params.robots "noindex" }} - - {{ end }} - - {{ if and (isset .Params "algolia") (isset .Params.algolia "importance") }} - - {{ else }} - - {{ end }} - - - - - {{ $canonicalLink := replace .Permalink .Site.BaseURL .Site.Params.CanonicalBaseUrl }} - - - - - - - - - - - - {{ if .HasShortcode "github_star_button" }} - - {{ end }} - - {{ partial "head_tracking.html" . }} - - {{ $scss := resources.Get "scss/main.scss" }} - {{ $options := (dict "outputStyle" "compressed" "enableSourceMap" (eq hugo.Environment "development")) }} - {{ $css := $scss | toCSS $options }} - - - - - - - - - - {{ partial "header.html" . }} - - - -
- - - -
-
-
- {{ if .Params.diffTitle }} - {{ if .Params.diffTitleName }} -

{{ .Params.diffTitleName }}

- {{ end }} - {{ else }} -

{{ .Title }}

+ + + + + + + + + + {{ if .HasShortcode "github_star_button" }} + + {{ end }} + + {{ partial "head_tracking.html" . }} + + {{ $scss := resources.Get "scss/main.scss" }} + {{ $options := (dict "outputStyle" "compressed" "enableSourceMap" (eq hugo.Environment "development")) }} + {{ $css := $scss | toCSS $options }} + + + + + + + + + + {{ partial "header.html" . }} + + + +
+ + + +
+
+
+ {{ if .Params.diffTitle }} + {{ if .Params.diffTitleName }} +

{{ .Params.diffTitleName }}

+ {{ end }} + {{ else }} +

{{ .Title }}

+ {{ end }} + + {{- partial "page_metadata.html" . -}} + + +
+ {{ block "content" . }} + {{ .Content }} {{ end }} - - {{- partial "page_metadata.html" . -}} - - -
- {{ block "content" . }} - {{ .Content }} - {{ end }} -
- {{- partial "related_posts.html" . -}} - {{- if not .Params.landingPage -}} - {{- partial "contribute_data.html" . -}} - - {{ end }} -
- - -
- -
- {{ partial "footer.html" . }} -
+ {{- partial "related_posts.html" . -}} + {{- if not .Params.landingPage -}} + {{- partial "contribute_data.html" . -}} + + {{ end }} +
-
- Return to Top + +
+ +
+ {{ partial "footer.html" . }} +
+ + + + +
+
+ + + {{ partial "footer_scripts.html" . }} + + + + {{ partial "cookie_notice.html" . }} + + {{ if .Page.Store.Get "hasMermaid" }} + + {{ end }} + + + + + + + + - - {{ partial "footer_scripts.html" . }} + + + + + + - - {{ partial "cookie_notice.html" . }} + - {{ end }} + function toggleChat() { + const modal = document.getElementById('chat-modal'); + const isVisible = modal.style.display === 'block'; + modal.style.display = isVisible ? 'none' : 'block'; - - + if (!botStarted && !isVisible) { + startChat(); + botStarted = true; + } + } + + function startChat() { + botui = new BotUI('botui-app'); + + botui.message.add({ + content: "Hi! Ask me anything about our docs." + }).then(() => askQuestion()); + } + + function askQuestion() { + botui.action.text({ + action: { + placeholder: 'Ask a question...' + } + }).then(res => { + const question = res.value; + + botui.message.add({ loading: true }); + + fetch('https://ai-gateway.tyk.technology/ai/anthropic-gateway/v1', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer BGmKqvWkjeaRuVENW_XuM3J0Pwk_4x0DspWfsWm73wo=' + }, + body: JSON.stringify({ + model: 'anthropic-gateway', + messages: [{ role: 'user', content: question }] + }) + }) + .then(r => r.json()) + .then(data => { + const reply = data.choices?.[0]?.message?.content || "Sorry, I didn't get that."; + botui.message.remove(1); // remove loading + botui.message.add({ content: reply }).then(() => askQuestion()); + }) + .catch(() => { + botui.message.remove(1); + botui.message.add({ content: "Oops! Something went wrong." }); + }); + }); + } + + + + + \ No newline at end of file From c7ae4129644e412100d5c24063003210aceb0092 Mon Sep 17 00:00:00 2001 From: Sharad Regoti Date: Thu, 17 Apr 2025 12:31:44 +0530 Subject: [PATCH 02/16] Fixes --- tyk-docs/themes/tykio/layouts/_default/baseof.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tyk-docs/themes/tykio/layouts/_default/baseof.html b/tyk-docs/themes/tykio/layouts/_default/baseof.html index 7511306de4..d3e6454410 100755 --- a/tyk-docs/themes/tykio/layouts/_default/baseof.html +++ b/tyk-docs/themes/tykio/layouts/_default/baseof.html @@ -283,7 +283,7 @@

{{ .Title }}

method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer BGmKqvWkjeaRuVENW_XuM3J0Pwk_4x0DspWfsWm73wo=' + 'Authorization': 'Bearer a=' }, body: JSON.stringify({ model: 'anthropic-gateway', From fcdfe91b6697ee5ffa25b259e4de00ce5c4bc92e Mon Sep 17 00:00:00 2001 From: Sharad Regoti Date: Wed, 23 Apr 2025 18:49:40 +0530 Subject: [PATCH 03/16] Fixes --- tyk-docs/static/css/chat.css | 0 tyk-docs/static/js/chat.js | 0 .../themes/tykio/layouts/_default/baseof.html | 460 +++++++----------- .../layouts/partials/ai-chat-widget.html | 40 ++ 4 files changed, 212 insertions(+), 288 deletions(-) create mode 100644 tyk-docs/static/css/chat.css create mode 100644 tyk-docs/static/js/chat.js create mode 100644 tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html diff --git a/tyk-docs/static/css/chat.css b/tyk-docs/static/css/chat.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tyk-docs/static/js/chat.js b/tyk-docs/static/js/chat.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tyk-docs/themes/tykio/layouts/_default/baseof.html b/tyk-docs/themes/tykio/layouts/_default/baseof.html index d3e6454410..dee052147e 100755 --- a/tyk-docs/themes/tykio/layouts/_default/baseof.html +++ b/tyk-docs/themes/tykio/layouts/_default/baseof.html @@ -1,309 +1,193 @@ + + + + + {{ .Title }} + + + + + {{ partial "google_search_console_metadata.html" . }} + {{ partial "site_schema.html" . }} + + {{ if eq .Params.robots "noindex" }} + + {{ end }} + + {{ if and (isset .Params "algolia") (isset .Params.algolia "importance") }} + + {{ else }} + + {{ end }} - - - - - {{ .Title }} - - - - - {{ partial "google_search_console_metadata.html" . }} - {{ partial "site_schema.html" . }} - - {{ if eq .Params.robots "noindex" }} - - {{ end }} - - {{ if and (isset .Params "algolia") (isset .Params.algolia "importance") }} - - {{ else }} - - {{ end }} - - - - - - {{ $canonicalLink := replace .Permalink .Site.BaseURL .Site.Params.CanonicalBaseUrl }} - - - - - - - - - - - - {{ if .HasShortcode "github_star_button" }} - - {{ end }} - - {{ partial "head_tracking.html" . }} - - {{ $scss := resources.Get "scss/main.scss" }} - {{ $options := (dict "outputStyle" "compressed" "enableSourceMap" (eq hugo.Environment "development")) }} - {{ $css := $scss | toCSS $options }} - - - - - - - - - - {{ partial "header.html" . }} - - - -
- - - -
-
-
- {{ if .Params.diffTitle }} - {{ if .Params.diffTitleName }} -

{{ .Params.diffTitleName }}

- {{ end }} - {{ else }} -

{{ .Title }}

- {{ end }} - - {{- partial "page_metadata.html" . -}} - - -
- {{ block "content" . }} - {{ .Content }} + + + + + + + + + + {{ if .HasShortcode "github_star_button" }} + + {{ end }} + + {{ partial "head_tracking.html" . }} + + {{ $scss := resources.Get "scss/main.scss" }} + {{ $options := (dict "outputStyle" "compressed" "enableSourceMap" (eq hugo.Environment "development")) }} + {{ $css := $scss | toCSS $options }} + + + + + + + + + + {{ partial "header.html" . }} + + + +
+ + + +
+
+
+ {{ if .Params.diffTitle }} + {{ if .Params.diffTitleName }} +

{{ .Params.diffTitleName }}

+ {{ end }} + {{ else }} +

{{ .Title }}

+ {{ end }} + + {{- partial "page_metadata.html" . -}} + + +
+ {{ block "content" . }} + {{ .Content }} + {{ end }} +
+ {{- partial "related_posts.html" . -}} + {{- if not .Params.landingPage -}} + {{- partial "contribute_data.html" . -}} + {{ end }}
- {{- partial "related_posts.html" . -}} - {{- if not .Params.landingPage -}} - {{- partial "contribute_data.html" . -}} - - {{ end }} -
- -
- -
- {{ partial "footer.html" . }} -
- - - - -
-
- - - {{ partial "footer_scripts.html" . }} - - - - {{ partial "cookie_notice.html" . }} - - {{ if .Page.Store.Get "hasMermaid" }} - - {{ end }} - - - - - - - - +
+ {{ partial "footer.html" . }} +
+ - - - - - - + + + - - - + + {{ partial "cookie_notice.html" . }} + + {{ if .Page.Store.Get "hasMermaid" }} + + {{ end }} + \ No newline at end of file diff --git a/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html b/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html new file mode 100644 index 0000000000..1a37f6829f --- /dev/null +++ b/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html @@ -0,0 +1,40 @@ + +
+
+ + + +
+ +
+ + + + + + + + \ No newline at end of file From 55685e541d8644be737c7a9a208457b6c21da970 Mon Sep 17 00:00:00 2001 From: Sharad Regoti Date: Wed, 23 Apr 2025 18:59:57 +0530 Subject: [PATCH 04/16] Fixes --- tyk-docs/static/css/chat.css | 217 ++++++++++++++ tyk-docs/static/js/chat.js | 264 ++++++++++++++++++ .../layouts/partials/ai-chat-widget.html | 8 +- 3 files changed, 483 insertions(+), 6 deletions(-) diff --git a/tyk-docs/static/css/chat.css b/tyk-docs/static/css/chat.css index e69de29bb2..327750b3d7 100644 --- a/tyk-docs/static/css/chat.css +++ b/tyk-docs/static/css/chat.css @@ -0,0 +1,217 @@ +/* Chat widget styles */ +.hidden { + display: none; +} + +#chat-widget-container { + position: fixed; + bottom: 20px; + right: 20px; + flex-direction: column; + z-index: 1000; +} + +#chat-popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 70vh; + max-height: 70vh; + width: 50%; + transition: all 0.3s; + overflow: hidden; + z-index: 1001; +} + +#chat-bubble { + z-index: 999; +} + +/* Typing indicator styles */ +.typing-indicator { + display: flex; + align-items: center; + margin-top: 5px; +} + +.typing-indicator span { + height: 8px; + width: 8px; + margin: 0 1px; + background-color: #9e9ea1; + display: block; + border-radius: 50%; + opacity: 0.4; +} + +.typing-indicator span:nth-of-type(1) { + animation: typing 1s infinite; +} + +.typing-indicator span:nth-of-type(2) { + animation: typing 1s 0.33s infinite; +} + +.typing-indicator span:nth-of-type(3) { + animation: typing 1s 0.66s infinite; +} + +@keyframes typing { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-5px); + } +} + +/* Markdown Styles */ +.markdown-content { + font-size: 14px; + line-height: 1.6; + overflow-wrap: break-word; + word-wrap: break-word; +} + +.markdown-content h1 { + font-size: 1.5em; + font-weight: bold; + margin: 0.67em 0; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} + +.markdown-content h2 { + font-size: 1.3em; + font-weight: bold; + margin: 0.83em 0; + border-bottom: 1px solid #eaecef; + padding-bottom: 0.3em; +} + +.markdown-content h3 { + font-size: 1.1em; + font-weight: bold; + margin: 1em 0; +} + +.markdown-content h4, .markdown-content h5, .markdown-content h6 { + font-weight: bold; + margin: 1.33em 0; +} + +.markdown-content p { + margin: 1em 0; +} + +.markdown-content ul, .markdown-content ol { + margin: 1em 0; + padding-left: 2em; +} + +.markdown-content ul { + list-style-type: disc; +} + +.markdown-content ol { + list-style-type: decimal; +} + +.markdown-content li { + margin: 0.5em 0; +} + +.markdown-content a { + color: #0366d6; + text-decoration: none; +} + +.markdown-content a:hover { + text-decoration: underline; +} + +.markdown-content code { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; + font-size: 85%; + padding: 0.2em 0.4em; + margin: 0; +} + +.markdown-content pre { + background-color: #f6f8fa; + border-radius: 3px; + font-size: 85%; + line-height: 1.45; + overflow: auto; + padding: 16px; + margin: 1em 0; +} + +.markdown-content pre code { + background-color: transparent; + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + overflow: visible; +} + +.markdown-content blockquote { + border-left: 0.25em solid #dfe2e5; + color: #6a737d; + padding: 0 1em; + margin: 1em 0; +} + +.markdown-content table { + border-collapse: collapse; + margin: 1em 0; + width: 100%; + overflow: auto; +} + +.markdown-content table th, .markdown-content table td { + border: 1px solid #dfe2e5; + padding: 6px 13px; +} + +.markdown-content table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-content table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-content img { + max-width: 100%; + box-sizing: content-box; +} + +.markdown-content hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +@media (max-width: 900px) { + #chat-popup { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + max-height: 100%; + border-radius: 0; + transform: none; + } +} diff --git a/tyk-docs/static/js/chat.js b/tyk-docs/static/js/chat.js index e69de29bb2..2b2f187678 100644 --- a/tyk-docs/static/js/chat.js +++ b/tyk-docs/static/js/chat.js @@ -0,0 +1,264 @@ +// Chat widget functionality +document.addEventListener('DOMContentLoaded', function () { + // Get DOM elements + const chatInput = document.getElementById('chat-input'); + const chatSubmit = document.getElementById('chat-submit'); + const chatMessages = document.getElementById('chat-messages'); + const chatBubble = document.getElementById('chat-bubble'); + const chatPopup = document.getElementById('chat-popup'); + const closePopup = document.getElementById('close-popup'); + + // Configure marked.js options + marked.setOptions({ + renderer: new marked.Renderer(), + highlight: function (code, language) { + const validLanguage = hljs.getLanguage(language) ? language : 'plaintext'; + return hljs.highlight(code, { language: validLanguage }).value; + }, + pedantic: false, + gfm: true, + breaks: true, + sanitize: false, + smartypants: false, + xhtml: false + }); + + // Add event listeners + chatSubmit.addEventListener('click', function () { + const message = chatInput.value.trim(); + if (!message) return; + + chatMessages.scrollTop = chatMessages.scrollHeight; + chatInput.value = ''; + onUserRequest(message); + }); + + chatInput.addEventListener('keyup', function (event) { + if (event.key === 'Enter') { + chatSubmit.click(); + } + }); + + chatBubble.addEventListener('click', function () { + togglePopup(); + }); + + closePopup.addEventListener('click', function () { + togglePopup(); + }); + + // Toggle chat popup visibility + function togglePopup() { + chatPopup.classList.toggle('hidden'); + if (!chatPopup.classList.contains('hidden')) { + chatInput.focus(); + } + } + + // Handle user message and API call + function onUserRequest(message) { + // Display user message + const messageElement = document.createElement('div'); + messageElement.className = 'flex justify-end mb-3'; + messageElement.innerHTML = ` +
+ ${message} +
+ `; + chatMessages.appendChild(messageElement); + chatMessages.scrollTop = chatMessages.scrollHeight; + + // Handle streaming response + handleStreamingResponse(message); + } + + // Handle streaming response + function handleStreamingResponse(message) { + // Create a placeholder for the bot's response with typing indicator + const replyElement = document.createElement('div'); + replyElement.className = 'flex mb-3'; + replyElement.innerHTML = ` +
+
+ + + +
+
+
+ `; + chatMessages.appendChild(replyElement); + chatMessages.scrollTop = chatMessages.scrollHeight; + + // Get the message container and content container + const messageContainer = replyElement.querySelector('.bg-gray-200'); + const rawContentContainer = replyElement.querySelector('.raw-content'); + + // Set up headers for SSE + const headers = new Headers({ + 'Accept': 'text/event-stream', + 'Content-Type': 'application/json' + }); + + // Create a controller to abort the fetch if needed + const controller = new AbortController(); + const signal = controller.signal; + + // Make a POST request to the streaming endpoint + fetch('http://localhost:3000/api/stream', { + method: 'POST', + headers: headers, + body: JSON.stringify({ prompt: message }), + signal: signal + }).then(response => { + // Create a reader for the response body + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + // Initialize accumulated text for the message + let accumulatedText = ''; + + // Function to process the stream + function processStream() { + // Read from the stream + reader.read().then(({ done, value }) => { + if (done) { + console.log('Stream complete'); + + // Remove typing indicator when done + const typingIndicator = messageContainer.querySelector('.typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + + // Replace the raw content with properly rendered markdown + try { + // Create a new div for the markdown content + const markdownContainer = document.createElement('div'); + markdownContainer.className = 'markdown-content'; + markdownContainer.innerHTML = marked.parse(accumulatedText); + + // Replace the raw content with the markdown content + rawContentContainer.replaceWith(markdownContainer); + + // Apply syntax highlighting to code blocks + messageContainer.querySelectorAll('pre code').forEach((block) => { + hljs.highlightElement(block); + }); + } catch (error) { + console.error('Error rendering final markdown:', error); + // Keep the raw content if there's an error + } + + return; + } + + // Decode the chunk and add it to our buffer + buffer += decoder.decode(value, { stream: true }); + + // Process any complete SSE messages in the buffer + const lines = buffer.split('\n\n'); + buffer = lines.pop() || ''; // Keep the last incomplete chunk in the buffer + + // Process each complete message + for (const line of lines) { + if (line.trim() === '') continue; + + // Extract the data part + const dataMatch = line.match(/^data: (.+)$/m); + if (!dataMatch) continue; + + const data = dataMatch[1]; + + // Check if the stream is done + if (data === '[DONE]') { + // Remove typing indicator + const typingIndicator = messageContainer.querySelector('.typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + + // Replace the raw content with properly rendered markdown + try { + // Create a new div for the markdown content + const markdownContainer = document.createElement('div'); + markdownContainer.className = 'markdown-content'; + markdownContainer.innerHTML = marked.parse(accumulatedText); + + // Replace the raw content with the markdown content + rawContentContainer.replaceWith(markdownContainer); + + // Apply syntax highlighting to code blocks + messageContainer.querySelectorAll('pre code').forEach((block) => { + hljs.highlightElement(block); + }); + } catch (error) { + console.error('Error rendering final markdown:', error); + // Keep the raw content if there's an error + } + + return; + } + + try { + // Parse the JSON data + const parsedData = JSON.parse(data); + + // Append the new text chunk + if (parsedData.text) { + // Remove typing indicator after first chunk + const typingIndicator = messageContainer.querySelector('.typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + + // Update accumulated text + accumulatedText += parsedData.text; + + // Update the raw content display + rawContentContainer.textContent = accumulatedText; + + // Scroll to bottom as text streams in + chatMessages.scrollTop = chatMessages.scrollHeight; + } + } catch (error) { + console.error('Error parsing SSE data:', error); + } + } + + // Continue reading from the stream + processStream(); + }).catch(error => { + console.error('Error reading from stream:', error); + + // Remove typing indicator + const typingIndicator = messageContainer.querySelector('.typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + + // Show error message if no text has been received yet + if (!accumulatedText) { + rawContentContainer.textContent = 'Sorry, something went wrong with the streaming connection.'; + } + }); + } + + // Start processing the stream + processStream(); + }).catch(error => { + console.error('Error fetching stream:', error); + + // Remove typing indicator + const typingIndicator = messageContainer.querySelector('.typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + + // Show error message + rawContentContainer.textContent = 'Sorry, something went wrong with the streaming connection.'; + }); + } + }); + \ No newline at end of file diff --git a/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html b/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html index 1a37f6829f..9f5209ba0d 100644 --- a/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html +++ b/tyk-docs/themes/tykio/layouts/partials/ai-chat-widget.html @@ -1,12 +1,8 @@
- - - + class="w-64 h-64 bg-gray-800 rounded-full flex items-center justify-center cursor-pointer text-3xl text-white"> + Ask AI