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
COPY . .
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
from fastapi import Body, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
@@ -35,6 +36,14 @@ class PredictResponse(BaseModel):
# --- FastAPI App ---
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def get_model_for_n(n: int):
"""
@@ -104,7 +113,7 @@ async def read_root():
def run():
# Read port from environment variable, default to 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__":

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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" />
<style>
body {
@@ -170,7 +170,7 @@
<div class="section">
<h2>Base URL</h2>
<div class="code-block">
<pre>http://localhost:8000</pre>
<pre>https://kreatyw.krzak.org</pre>
</div>
</div>

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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" />
@@ -144,31 +144,12 @@
id="editor"
spellcheck="false"
autofocus
placeholder="Start writing something poetic..."
placeholder="Start typing here..."
></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>
</main>
</div>
<script src="/ui/script.js"></script>
<script src="/ui/script.js?v=2"></script>
</body>
</html>

View File

@@ -14,7 +14,6 @@ document.addEventListener("DOMContentLoaded", () => {
const generateBtn = document.getElementById("generate-more-btn");
const sidebarToggle = document.getElementById("sidebar-toggle");
const sidebar = document.getElementById("sidebar");
const acceptSuggestionBtn = document.getElementById("accept-suggestion-btn");
let currentSuggestion = "";
let isFetching = false;
@@ -67,7 +66,7 @@ document.addEventListener("DOMContentLoaded", () => {
isFetching = true;
status.textContent = "Thinking...";
statusIndicator.classList.add("fetching");
if (statusIndicator) statusIndicator.classList.add("fetching");
const n = parseInt(nGramSelect.value);
const temperature = parseFloat(tempInput.value);
@@ -92,24 +91,38 @@ document.addEventListener("DOMContentLoaded", () => {
}
} catch (error) {
console.error("Prediction failed:", error);
status.textContent = "Error";
status.textContent = "Error: " + error.message;
} finally {
isFetching = false;
status.textContent = "Idle";
statusIndicator.classList.remove("fetching");
if (statusIndicator) statusIndicator.classList.remove("fetching");
}
};
const updateSuggestion = () => {
suggestionOverlay.innerHTML = "";
const editorText = editor.value;
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) {
acceptSuggestionBtn.classList.add("visible");
} else {
acceptSuggestionBtn.classList.remove("visible");
const suggestionSpan = document.createElement("span");
suggestionSpan.textContent = currentSuggestion;
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", () => {
if (currentSuggestion) {
// Handle click on suggestion
suggestionOverlay.addEventListener("click", (e) => {
if (e.target.classList.contains("suggestion-highlight")) {
insertText(currentSuggestion);
fetchPrediction(editor.value);
editor.focus();
}
});
generateBtn.addEventListener("click", () => {
fetchPrediction(editor.value, 50);
closeSidebarOnMobile();
});
if (generateBtn) {
generateBtn.addEventListener("click", () => {
fetchPrediction(editor.value, 50);
closeSidebarOnMobile();
});
}
// Sync 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 {
margin-bottom: 2.5rem;
}
@@ -376,7 +338,7 @@ label {
#suggestion-overlay {
width: 100%;
height: 100%;
padding: 2rem;
padding: 2rem 2rem 10rem 2rem;
font-family: "SF Mono", "Fira Code", monospace;
font-size: 1.1rem;
line-height: 1.8;
@@ -391,7 +353,7 @@ label {
@media (max-width: 768px) {
#editor,
#suggestion-overlay {
padding: 1rem;
padding: 1rem 1rem 10rem 1rem;
font-size: 1rem;
}
}
@@ -402,16 +364,59 @@ label {
color: var(--foreground);
outline: 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 {
position: absolute;
top: 0;
left: 0;
z-index: 1;
color: var(--muted-foreground);
z-index: 3;
color: transparent;
pointer-events: none;
opacity: 0.5;
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;
}