Compare commits
7 Commits
6f0977be50
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42864c5491 | ||
|
|
3131fd3531 | ||
|
|
d936ac872a | ||
|
|
019f85a29a | ||
|
|
87c56ba2f8 | ||
| 5bd7ccf76a | |||
| 630d8ebdc0 |
@@ -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
11
api.py
@@ -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__":
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
46
ui/script.js
46
ui/script.js
@@ -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", () => {
|
||||||
|
|||||||
91
ui/style.css
91
ui/style.css
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user