Hosting dependencies on server, added logo to welcome page and some fixs
This commit is contained in:
parent
fa1bd44a99
commit
f76ebd7491
3
static/dependencies/bulma.css
vendored
Normal file
3
static/dependencies/bulma.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
static/dependencies/default.css
Normal file
9
static/dependencies/default.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
Theme: Default
|
||||||
|
Description: Original highlight.js style
|
||||||
|
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
|
||||||
|
Maintainer: @highlightjs/core-team
|
||||||
|
Website: https://highlightjs.org/
|
||||||
|
License: see project LICENSE
|
||||||
|
Touched: 2021
|
||||||
|
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
1213
static/dependencies/highlight.js
Normal file
1213
static/dependencies/highlight.js
Normal file
File diff suppressed because one or more lines are too long
1
static/dependencies/htmx.js
Normal file
1
static/dependencies/htmx.js
Normal file
File diff suppressed because one or more lines are too long
6
static/dependencies/marked.js
Normal file
6
static/dependencies/marked.js
Normal file
File diff suppressed because one or more lines are too long
1
static/dependencies/pricing-table.js
Normal file
1
static/dependencies/pricing-table.js
Normal file
File diff suppressed because one or more lines are too long
24
static/dependencies/russo.css
Normal file
24
static/dependencies/russo.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/russoone/v16/Z9XUDmZRWg6M1LvRYsHOy8mJrrg.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/russoone/v16/Z9XUDmZRWg6M1LvRYsHOwcmJrrg.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/russoone/v16/Z9XUDmZRWg6M1LvRYsHOz8mJ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
2
static/dependencies/sortable.js
Normal file
2
static/dependencies/sortable.js
Normal file
File diff suppressed because one or more lines are too long
301
static/dependencies/sse.js
Normal file
301
static/dependencies/sse.js
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
/*
|
||||||
|
Server Sent Events Extension
|
||||||
|
============================
|
||||||
|
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api
|
||||||
|
|
||||||
|
htmx.defineExtension('sse', {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init saves the provided reference to the internal HTMX API.
|
||||||
|
*
|
||||||
|
* @param {import("../htmx").HtmxInternalApi} api
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
init: function(apiRef) {
|
||||||
|
// store a reference to the internal API.
|
||||||
|
api = apiRef
|
||||||
|
|
||||||
|
// set a function in the public API for creating new EventSource objects
|
||||||
|
if (htmx.createEventSource == undefined) {
|
||||||
|
htmx.createEventSource = createEventSource
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onEvent handles all events passed to this extension.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
var parent = evt.target || evt.detail.elt
|
||||||
|
switch (name) {
|
||||||
|
case 'htmx:beforeCleanupElement':
|
||||||
|
var internalData = api.getInternalData(parent)
|
||||||
|
// Try to remove remove an EventSource when elements are removed
|
||||||
|
if (internalData.sseEventSource) {
|
||||||
|
internalData.sseEventSource.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
// Try to create EventSources when elements are processed
|
||||||
|
case 'htmx:afterProcessNode':
|
||||||
|
ensureEventSourceOnElement(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/// ////////////////////////////////////////////
|
||||||
|
// HELPER FUNCTIONS
|
||||||
|
/// ////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createEventSource is the default method for creating new EventSource objects.
|
||||||
|
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns EventSource
|
||||||
|
*/
|
||||||
|
function createEventSource(url) {
|
||||||
|
return new EventSource(url, { withCredentials: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* registerSSE looks for attributes that can contain sse events, right
|
||||||
|
* now hx-trigger and sse-swap and adds listeners based on these attributes too
|
||||||
|
* the closest event source
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
*/
|
||||||
|
function registerSSE(elt) {
|
||||||
|
// Add message handlers for every `sse-swap` attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, 'sse-swap').forEach(function(child) {
|
||||||
|
// Find closest existing event source
|
||||||
|
var sourceElement = api.getClosestMatch(child, hasEventSource)
|
||||||
|
if (sourceElement == null) {
|
||||||
|
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||||
|
return null // no eventsource in parentage, orphaned element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set internalData and source
|
||||||
|
var internalData = api.getInternalData(sourceElement)
|
||||||
|
var source = internalData.sseEventSource
|
||||||
|
|
||||||
|
var sseSwapAttr = api.getAttributeValue(child, 'sse-swap')
|
||||||
|
var sseEventNames = sseSwapAttr.split(',')
|
||||||
|
|
||||||
|
for (var i = 0; i < sseEventNames.length; i++) {
|
||||||
|
var sseEventName = sseEventNames[i].trim()
|
||||||
|
var listener = function(event) {
|
||||||
|
// If the source is missing then close SSE
|
||||||
|
if (maybeCloseSSESource(sourceElement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the body no longer contains the element, remove the listener
|
||||||
|
if (!api.bodyContains(child)) {
|
||||||
|
source.removeEventListener(sseEventName, listener)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap the response into the DOM and trigger a notification
|
||||||
|
if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swap(child, event.data)
|
||||||
|
api.triggerEvent(elt, 'htmx:sseMessage', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the new listener
|
||||||
|
api.getInternalData(child).sseEventListener = listener
|
||||||
|
source.addEventListener(sseEventName, listener)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add message handlers for every `hx-trigger="sse:*"` attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, 'hx-trigger').forEach(function(child) {
|
||||||
|
// Find closest existing event source
|
||||||
|
var sourceElement = api.getClosestMatch(child, hasEventSource)
|
||||||
|
if (sourceElement == null) {
|
||||||
|
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
|
||||||
|
return null // no eventsource in parentage, orphaned element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set internalData and source
|
||||||
|
var internalData = api.getInternalData(sourceElement)
|
||||||
|
var source = internalData.sseEventSource
|
||||||
|
|
||||||
|
var sseEventName = api.getAttributeValue(child, 'hx-trigger')
|
||||||
|
if (sseEventName == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process hx-triggers for events with the "sse:" prefix
|
||||||
|
if (sseEventName.slice(0, 4) != 'sse:') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener = function(event) {
|
||||||
|
if (maybeCloseSSESource(sourceElement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api.bodyContains(child)) {
|
||||||
|
source.removeEventListener(sseEventName, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger events to be handled by the rest of htmx
|
||||||
|
htmx.trigger(child, sseEventName, event)
|
||||||
|
htmx.trigger(child, 'htmx:sseMessage', event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the new listener
|
||||||
|
api.getInternalData(elt).sseEventListener = listener
|
||||||
|
source.addEventListener(sseEventName.slice(4), listener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
|
||||||
|
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
|
||||||
|
* is created and stored in the element's internalData.
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {number} retryCount
|
||||||
|
* @returns {EventSource | null}
|
||||||
|
*/
|
||||||
|
function ensureEventSourceOnElement(elt, retryCount) {
|
||||||
|
if (elt == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extension source creation attribute
|
||||||
|
queryAttributeOnThisOrChildren(elt, 'sse-connect').forEach(function(child) {
|
||||||
|
var sseURL = api.getAttributeValue(child, 'sse-connect')
|
||||||
|
if (sseURL == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureEventSource(child, sseURL, retryCount)
|
||||||
|
})
|
||||||
|
|
||||||
|
registerSSE(elt)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEventSource(elt, url, retryCount) {
|
||||||
|
var source = htmx.createEventSource(url)
|
||||||
|
|
||||||
|
source.onerror = function(err) {
|
||||||
|
// Log an error event
|
||||||
|
api.triggerErrorEvent(elt, 'htmx:sseError', { error: err, source })
|
||||||
|
|
||||||
|
// If parent no longer exists in the document, then clean up this EventSource
|
||||||
|
if (maybeCloseSSESource(elt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try to reconnect the EventSource
|
||||||
|
if (source.readyState === EventSource.CLOSED) {
|
||||||
|
retryCount = retryCount || 0
|
||||||
|
var timeout = Math.random() * (2 ^ retryCount) * 500
|
||||||
|
window.setTimeout(function() {
|
||||||
|
ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1))
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.onopen = function(evt) {
|
||||||
|
api.triggerEvent(elt, 'htmx:sseOpen', { source })
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getInternalData(elt).sseEventSource = source
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybeCloseSSESource confirms that the parent element still exists.
|
||||||
|
* If not, then any associated SSE source is closed and the function returns true.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
function maybeCloseSSESource(elt) {
|
||||||
|
if (!api.bodyContains(elt)) {
|
||||||
|
var source = api.getInternalData(elt).sseEventSource
|
||||||
|
if (source != undefined) {
|
||||||
|
source.close()
|
||||||
|
// source = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} attributeName
|
||||||
|
*/
|
||||||
|
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||||
|
var result = []
|
||||||
|
|
||||||
|
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||||
|
if (api.hasAttribute(elt, attributeName)) {
|
||||||
|
result.push(elt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all child nodes that match the requested attribute
|
||||||
|
elt.querySelectorAll('[' + attributeName + '], [data-' + attributeName + ']').forEach(function(node) {
|
||||||
|
result.push(node)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} content
|
||||||
|
*/
|
||||||
|
function swap(elt, content) {
|
||||||
|
api.withExtensions(elt, function(extension) {
|
||||||
|
content = extension.transformResponse(content, null, elt)
|
||||||
|
})
|
||||||
|
|
||||||
|
var swapSpec = api.getSwapSpecification(elt)
|
||||||
|
var target = api.getTarget(elt)
|
||||||
|
api.swap(target, content, swapSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* doSettle mirrors much of the functionality in htmx that
|
||||||
|
* settles elements after their content has been swapped.
|
||||||
|
* TODO: this should be published by htmx, and not duplicated here
|
||||||
|
* @param {import("../htmx").HtmxSettleInfo} settleInfo
|
||||||
|
* @returns () => void
|
||||||
|
*/
|
||||||
|
function doSettle(settleInfo) {
|
||||||
|
return function() {
|
||||||
|
settleInfo.tasks.forEach(function(task) {
|
||||||
|
task.call()
|
||||||
|
})
|
||||||
|
|
||||||
|
settleInfo.elts.forEach(function(elt) {
|
||||||
|
if (elt.classList) {
|
||||||
|
elt.classList.remove(htmx.config.settlingClass)
|
||||||
|
}
|
||||||
|
api.triggerEvent(elt, 'htmx:afterSettle')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEventSource(node) {
|
||||||
|
return api.getInternalData(node).sseEventSource != null
|
||||||
|
}
|
||||||
|
})()
|
@ -3,6 +3,25 @@ html {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mylogo {
|
||||||
|
font-family: 'Russo One', sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mylogo text {
|
||||||
|
text-transform: uppercase;
|
||||||
|
fill: #fff;
|
||||||
|
font-size: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Stuff for message boxes */
|
/* Stuff for message boxes */
|
||||||
#chat-messages .message-content pre {
|
#chat-messages .message-content pre {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
@ -6,28 +6,21 @@
|
|||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||||||
<title>JADE</title>
|
<title>JADE</title>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" href="dependencies/bulma.css">
|
||||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
<script src="https://kit.fontawesome.com/f515f7ad51.js"
|
||||||
<link rel="stylesheet"
|
crossorigin="anonymous"></script>
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
|
<link rel="stylesheet" href="dependencies/russo.css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Russo+One"
|
|
||||||
rel="stylesheet">
|
|
||||||
|
|
||||||
<!--link rel="stylesheet" href="/animations.css"-->
|
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<script src="dependencies/htmx.js"></script>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.0-beta4/dist/htmx.js"></script>
|
<script src="dependencies/sse.js"></script>
|
||||||
<script src="https://unpkg.com/htmx-ext-sse@2.0.0/sse.js"></script>
|
<script src="dependencies/sortable.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js"></script>
|
<script src="dependencies/pricing-table.js"></script>
|
||||||
|
|
||||||
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
|
|
||||||
|
|
||||||
<!-- highlight.js -->
|
<!-- highlight.js -->
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" href="dependencies/default.css">
|
||||||
href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/default.min.css">
|
<script src="dependencies/highlight.js"></script>
|
||||||
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js"></script>
|
|
||||||
<script>hljs.highlightAll();</script>
|
<script>hljs.highlightAll();</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="dependencies/marked.js"></script>
|
||||||
|
|
||||||
<script src="wasm_exec.js"></script>
|
<script src="wasm_exec.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
responses to choose the best one.
|
responses to choose the best one.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
The selected response can be used as the basis for the next message across
|
The selected response can be used for the next message across
|
||||||
all models. For example, a response from GPT-4 can be used by Claude Haiku
|
all models. For example, a response from GPT-4 can be used by Claude Haiku
|
||||||
in the next interaction.
|
in the next interaction.
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
<h1 class="title is-1">JADE: Simple Multi-Model Chatbot</h1>
|
<div class="logo-container">
|
||||||
|
<svg class="mylogo" viewBox="0 0 1320 500">
|
||||||
|
<text x="50%" y="50%" dy=".35em" text-anchor="middle">
|
||||||
|
JADE
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 class="title is-1">Cheap Multi-Model Chatbot</h1>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
@ -21,7 +28,7 @@
|
|||||||
|
|
||||||
<p>For example, a response from GPT-4 Omni can be used by Claude Haiku.</p>
|
<p>For example, a response from GPT-4 Omni can be used by Claude Haiku.</p>
|
||||||
|
|
||||||
<a class="button is-primary mt-2 mb-2" href="/signin">Try JADE now for free!</a>
|
<a class="button is-primary mt-2 mb-2" href="/signin">Login</a>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user