Compare commits

..

7 Commits

Author SHA1 Message Date
N0\A
42864c5491 Should fully work now 2026-01-09 00:47:49 +01:00
N0\A
3131fd3531 Hopefully? 2026-01-09 00:45:01 +01:00
N0\A
d936ac872a Maybe now? 2026-01-09 00:40:25 +01:00
N0\A
019f85a29a HTTPS fix 2026-01-09 00:35:21 +01:00
N0\A
87c56ba2f8 Better suggestion accept on mobile and word wrap offset fix 2026-01-09 00:24:38 +01:00
5bd7ccf76a Update index.html 2026-01-07 11:17:49 +01:00
630d8ebdc0 Update api.html 2026-01-07 11:02:22 +01:00
6 changed files with 95 additions and 84 deletions

View File

@@ -5,4 +5,4 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY . . COPY . .
EXPOSE 8000 EXPOSE 8000
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"] CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]

11
api.py
View File

@@ -3,6 +3,7 @@ import sys
import uvicorn import uvicorn
from fastapi import Body, FastAPI from fastapi import Body, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel from pydantic import BaseModel
@@ -35,6 +36,14 @@ class PredictResponse(BaseModel):
# --- FastAPI App --- # --- FastAPI App ---
app = FastAPI() app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def get_model_for_n(n: int): def get_model_for_n(n: int):
""" """
@@ -104,7 +113,7 @@ async def read_root():
def run(): def run():
# Read port from environment variable, default to 8000 # Read port from environment variable, default to 8000
port = int(os.environ.get("PORT", 8000)) port = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port) uvicorn.run(app, host="0.0.0.0", port=port, proxy_headers=True, forwarded_allow_ips="*")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kreatyw - API Documentation</title> <title>Kreatyw - API Documentation</title>
<link rel="stylesheet" href="/ui/style.css" /> <link rel="stylesheet" href="/ui/style.css?v=2" />
<link rel="icon" type="image/x-icon" href="/ui/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/ui/favicon.ico" />
<style> <style>
body { body {
@@ -170,7 +170,7 @@
<div class="section"> <div class="section">
<h2>Base URL</h2> <h2>Base URL</h2>
<div class="code-block"> <div class="code-block">
<pre>http://localhost:8000</pre> <pre>https://kreatyw.krzak.org</pre>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kreatyw</title> <title>Kreatyw</title>
<link rel="stylesheet" href="/ui/style.css" /> <link rel="stylesheet" href="/ui/style.css?v=2" />
<link rel="icon" type="image/x-icon" href="/ui/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/ui/favicon.ico" />
@@ -144,31 +144,12 @@
id="editor" id="editor"
spellcheck="false" spellcheck="false"
autofocus autofocus
placeholder="Start writing something poetic..." placeholder="Start typing here..."
></textarea> ></textarea>
<button
id="accept-suggestion-btn"
class="btn-floating"
title="Accept Suggestion"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</button>
</div> </div>
</div> </div>
</main> </main>
</div> </div>
<script src="/ui/script.js"></script> <script src="/ui/script.js?v=2"></script>
</body> </body>
</html> </html>

View File

@@ -14,7 +14,6 @@ document.addEventListener("DOMContentLoaded", () => {
const generateBtn = document.getElementById("generate-more-btn"); const generateBtn = document.getElementById("generate-more-btn");
const sidebarToggle = document.getElementById("sidebar-toggle"); const sidebarToggle = document.getElementById("sidebar-toggle");
const sidebar = document.getElementById("sidebar"); const sidebar = document.getElementById("sidebar");
const acceptSuggestionBtn = document.getElementById("accept-suggestion-btn");
let currentSuggestion = ""; let currentSuggestion = "";
let isFetching = false; let isFetching = false;
@@ -67,7 +66,7 @@ document.addEventListener("DOMContentLoaded", () => {
isFetching = true; isFetching = true;
status.textContent = "Thinking..."; status.textContent = "Thinking...";
statusIndicator.classList.add("fetching"); if (statusIndicator) statusIndicator.classList.add("fetching");
const n = parseInt(nGramSelect.value); const n = parseInt(nGramSelect.value);
const temperature = parseFloat(tempInput.value); const temperature = parseFloat(tempInput.value);
@@ -92,24 +91,38 @@ document.addEventListener("DOMContentLoaded", () => {
} }
} catch (error) { } catch (error) {
console.error("Prediction failed:", error); console.error("Prediction failed:", error);
status.textContent = "Error"; status.textContent = "Error: " + error.message;
} finally { } finally {
isFetching = false; isFetching = false;
status.textContent = "Idle"; status.textContent = "Idle";
statusIndicator.classList.remove("fetching"); if (statusIndicator) statusIndicator.classList.remove("fetching");
} }
}; };
const updateSuggestion = () => { const updateSuggestion = () => {
suggestionOverlay.innerHTML = "";
const editorText = editor.value; const editorText = editor.value;
const space = editorText.length > 0 && !/\s$/.test(editorText) ? " " : ""; const space = editorText.length > 0 && !/\s$/.test(editorText) ? " " : "";
suggestionOverlay.textContent = editorText + space + currentSuggestion;
// Show/hide accept button // Create invisible text span to match editor content exactly
const textSpan = document.createElement("span");
textSpan.textContent = editorText + space;
textSpan.style.color = "transparent";
suggestionOverlay.appendChild(textSpan);
if (currentSuggestion) { if (currentSuggestion) {
acceptSuggestionBtn.classList.add("visible"); const suggestionSpan = document.createElement("span");
} else { suggestionSpan.textContent = currentSuggestion;
acceptSuggestionBtn.classList.remove("visible"); suggestionSpan.className = "suggestion-highlight";
// Add Tab Icon (Better SVG for Tab Key)
const iconSpan = document.createElement("span");
iconSpan.style.pointerEvents = "none"; // Ensure clicks pass through to suggestionSpan
iconSpan.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tab-icon"><path d="M21 9V15"/><path d="M3 12H17"/><path d="m14 9 3 3-3 3"/></svg>`;
suggestionSpan.appendChild(iconSpan);
suggestionOverlay.appendChild(suggestionSpan);
} }
}; };
@@ -145,18 +158,21 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
acceptSuggestionBtn.addEventListener("click", () => { // Handle click on suggestion
if (currentSuggestion) { suggestionOverlay.addEventListener("click", (e) => {
if (e.target.classList.contains("suggestion-highlight")) {
insertText(currentSuggestion); insertText(currentSuggestion);
fetchPrediction(editor.value); fetchPrediction(editor.value);
editor.focus(); editor.focus();
} }
}); });
generateBtn.addEventListener("click", () => { if (generateBtn) {
fetchPrediction(editor.value, 50); generateBtn.addEventListener("click", () => {
closeSidebarOnMobile(); fetchPrediction(editor.value, 50);
}); closeSidebarOnMobile();
});
}
// Sync scroll // Sync scroll
editor.addEventListener("scroll", () => { editor.addEventListener("scroll", () => {

View File

@@ -117,44 +117,6 @@ body {
} }
} }
/* Floating Action Button for Suggestions */
.btn-floating {
position: absolute;
bottom: 1.5rem;
right: 1.5rem;
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
background-color: var(--primary);
color: var(--primary-foreground);
border: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
cursor: pointer;
display: none; /* Hidden by default, shown when suggestion exists */
align-items: center;
justify-content: center;
z-index: 10;
transition:
transform 0.2s,
opacity 0.2s;
}
.btn-floating:active {
transform: scale(0.95);
}
@media (max-width: 768px) {
.btn-floating.visible {
display: flex;
}
}
@media (min-width: 769px) {
.btn-floating.visible {
display: none;
}
}
.sidebar-header { .sidebar-header {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
@@ -376,7 +338,7 @@ label {
#suggestion-overlay { #suggestion-overlay {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 2rem; padding: 2rem 2rem 10rem 2rem;
font-family: "SF Mono", "Fira Code", monospace; font-family: "SF Mono", "Fira Code", monospace;
font-size: 1.1rem; font-size: 1.1rem;
line-height: 1.8; line-height: 1.8;
@@ -391,7 +353,7 @@ label {
@media (max-width: 768px) { @media (max-width: 768px) {
#editor, #editor,
#suggestion-overlay { #suggestion-overlay {
padding: 1rem; padding: 1rem 1rem 10rem 1rem;
font-size: 1rem; font-size: 1rem;
} }
} }
@@ -402,16 +364,59 @@ label {
color: var(--foreground); color: var(--foreground);
outline: none; outline: none;
resize: none; resize: none;
overflow-y: auto; overflow-y: scroll;
}
#editor::-webkit-scrollbar {
width: 16px;
}
#editor::-webkit-scrollbar-thumb {
background-color: var(--muted);
border: 4px solid var(--card);
border-radius: 8px;
}
#editor::-webkit-scrollbar-track {
background-color: transparent;
} }
#suggestion-overlay { #suggestion-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1; z-index: 3;
color: var(--muted-foreground); color: transparent;
pointer-events: none; pointer-events: none;
opacity: 0.5; opacity: 0.5;
overflow: hidden; overflow: hidden;
overflow-y: scroll;
}
#suggestion-overlay::-webkit-scrollbar {
width: 16px; /* Width must match standard scrollbar width to align text */
background: transparent;
}
#suggestion-overlay::-webkit-scrollbar-thumb {
background: transparent;
}
.suggestion-highlight {
color: var(--muted-foreground);
cursor: pointer;
pointer-events: auto;
}
.tab-icon {
display: inline-block;
width: 1.8em;
height: 1.2em;
vertical-align: middle;
margin-left: 0.5em;
opacity: 0.8;
border: 1px solid var(--muted-foreground);
border-radius: 4px;
padding: 2px;
pointer-events: none;
} }