feat: add ISSLOP AI usage analysis CLI commands, game router, and politics topic

- Add `cmd_isslop_prune`, `cmd_isslop_clear`, `cmd_isslop_analyze` CLI commands for AI usage analysis job management
- Register `/game` router with `index` and `farm` endpoints for Code Farm idle game
- Add `politics` to allowed TOPICS constant replacing `signals`
- Introduce `ISSLOP_DIR`, `ISSLOP_WORKSPACES_DIR`, `ISSLOP_RUNS_DIR`, `ISSLOP_MEDIA_DIR` config paths
- Add `clear_user_stars` and `clear_user_projects_cache` calls on vote and project create/delete
- Update `make prod` to use `nproc` workers via `DEVPLACE_WEB_WORKERS` env var
- Convert `database.py` and `utils.py` to packages for modular structure
- Add `devplace apikey` and `devplace token` CLI subcommands for API key and access token management
This commit is contained in:
2026-07-06 03:57:47 +00:00
parent f1bdefd834
commit 9a8046ab2a
110 changed files with 1466 additions and 374 deletions
+1 -1
View File
@@ -510,7 +510,7 @@ img {
.badge-rant { background: var(--topic-rant); color: var(--white); }
.badge-fun { background: var(--topic-fun); color: #000; }
.badge-random { background: var(--border-light); color: var(--text-secondary); }
.badge-signals { background: var(--topic-signals); color: var(--white); }
.badge-politics { background: var(--topic-politics); color: var(--white); }
.avatar {
width: 40px;
+91
View File
@@ -681,3 +681,94 @@ pre.code-pre > .code-gutter {
border-top: 1px solid var(--border);
text-align: center;
}
.docs-content h2[id],
.docs-content h3[id] {
scroll-margin-top: 80px;
}
.docs-heading-anchor {
margin-left: 8px;
color: var(--text-muted);
text-decoration: none;
font-weight: 400;
opacity: 0;
transition: opacity 0.15s ease;
}
.docs-content h2:hover .docs-heading-anchor,
.docs-content h3:hover .docs-heading-anchor {
opacity: 1;
}
.docs-heading-anchor:hover {
color: var(--accent);
}
.docs-toc {
margin: 0 0 24px;
padding: 20px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
.docs-toc-heading {
margin: 0 0 14px;
font-size: 1.05rem;
font-weight: 700;
color: var(--text-primary);
}
.docs-toc-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 10px;
list-style: none;
margin: 0;
padding: 0;
}
.docs-toc-item a {
display: flex;
flex-direction: column;
gap: 4px;
height: 100%;
padding: 12px 14px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
text-decoration: none;
transition: background 0.15s ease, border-color 0.15s ease;
}
.docs-toc-item a:hover {
background: var(--bg-card-hover);
border-color: var(--border-light);
}
.docs-toc-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
color: var(--text-primary);
font-weight: 600;
font-size: 0.92rem;
}
.docs-toc-count {
flex-shrink: 0;
padding: 1px 8px;
border-radius: 999px;
background: var(--accent-light);
color: var(--accent);
font-size: 0.72rem;
font-weight: 700;
white-space: nowrap;
}
.docs-toc-summary {
color: var(--text-secondary);
font-size: 0.82rem;
}
+1 -1
View File
@@ -62,7 +62,7 @@
--topic-question: #448aff;
--topic-rant: #ff5252;
--topic-fun: #ffab00;
--topic-signals: #00bcd4;
--topic-politics: #00bcd4;
--bg-gradient: linear-gradient(135deg, #080413 0%, #160a28 50%, #080413 100%);
}
+3 -1
View File
@@ -256,7 +256,9 @@ export class ApiTester {
buildNotes() {
if (!this.config.notes.length) return null;
const wrap = this.el("div", { class: "endpoint-notes" });
wrap.innerHTML = contentRenderer.render(this.config.notes.join("\n\n"));
contentRenderer.ready.then(() => {
wrap.innerHTML = contentRenderer.render(this.config.notes.join("\n\n"));
});
return wrap;
}
+1
View File
@@ -124,6 +124,7 @@ export class CommentManager {
const result = await Http.send(form.action, { content });
text.dataset.raw = result.content;
text.textContent = result.content;
await contentRenderer.ready;
contentRenderer.applyTo(text);
text.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
img.dataset.lightbox = "";
+8 -6
View File
@@ -14,14 +14,16 @@ export class ContentEnhancer {
}
initContentRenderer() {
document.querySelectorAll("[data-render]").forEach((el) => {
contentRenderer.applyTo(el);
el.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
img.dataset.lightbox = "";
contentRenderer.ready.then(() => {
document.querySelectorAll("[data-render]").forEach((el) => {
contentRenderer.applyTo(el);
el.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
img.dataset.lightbox = "";
});
});
contentRenderer.highlightAll();
this.enhanceCodeBlocks();
});
contentRenderer.highlightAll();
this.enhanceCodeBlocks();
}
enhanceCodeBlocks(root = document) {
+10 -3
View File
@@ -1,10 +1,9 @@
// retoor <retoor@molodetz.nl>
import { EMOJI_SHORTCODES } from "./emoji-shortcodes.js";
export class ContentRenderer {
constructor() {
this.emojiMap = EMOJI_SHORTCODES;
this.emojiMap = {};
this.emojiLoaded = false;
this.imageExtRe = /\.(jpg|jpeg|png|gif|webp|svg|bmp|webp)(\?.*)?$/i;
this.videoExtRe = /\.(mp4|webm|ogg|ogv|mov|m4v)(\?.*)?$/i;
this.audioExtRe = /\.(mp3|wav|flac|ogg|aac|m4a|wma|opus)(\?.*)?$/i;
@@ -257,4 +256,12 @@ export class ContentRenderer {
}
export const contentRenderer = new ContentRenderer();
contentRenderer.ready = import("./emoji-shortcodes.js")
.then((mod) => {
contentRenderer.emojiMap = mod.EMOJI_SHORTCODES;
})
.catch(() => {})
.finally(() => {
contentRenderer.emojiLoaded = true;
});
window.contentRenderer = contentRenderer;
+12
View File
@@ -6,6 +6,17 @@ import { assetUrl } from "./assetVersion.js";
const EMOJI_DATA_SOURCE = assetUrl("/static/vendor/emoji-picker-element/data.json");
let pickerElementModule = null;
function loadPickerElement() {
if (!pickerElementModule) {
pickerElementModule = import(
assetUrl("/static/vendor/emoji-picker-element/index.js")
).catch(() => {});
}
return pickerElementModule;
}
export class EmojiPicker {
constructor(textarea) {
this.textarea = textarea;
@@ -48,6 +59,7 @@ export class EmojiPicker {
}
toggle() {
loadPickerElement();
DomUtils.toggle(this.wrapper);
}
+3 -1
View File
@@ -22,7 +22,9 @@ export class Http {
static async _messageFrom(response) {
const data = await response.json().catch(() => ({}));
const message = (data && data.error && data.error.message) || `request failed with status ${response.status}`;
const message = (data && data.error && data.error.message)
|| (data && Array.isArray(data.messages) && data.messages.length ? data.messages.join(" ") : "")
|| `request failed with status ${response.status}`;
return { data, message };
}
+6 -4
View File
@@ -337,11 +337,13 @@ export class MessagesLayout {
}
renderBody(el) {
contentRenderer.applyTo(el);
el.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
img.dataset.lightbox = "";
contentRenderer.ready.then(() => {
contentRenderer.applyTo(el);
el.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
img.dataset.lightbox = "";
});
contentRenderer.highlightAll();
});
contentRenderer.highlightAll();
}
renderAttachments(attachments) {
@@ -12,6 +12,10 @@ export class AppContent extends Component {
window.addEventListener("load", () => this.connectedCallback(), { once: true });
return;
}
if (!contentRenderer.emojiLoaded) {
contentRenderer.ready.then(() => this.connectedCallback());
return;
}
this._rendered = true;
this._source = this.textContent;
this.classList.add("rendered-content");
@@ -12,6 +12,10 @@ export class AppTitle extends Component {
window.addEventListener("load", () => this.connectedCallback(), { once: true });
return;
}
if (!contentRenderer.emojiLoaded) {
contentRenderer.ready.then(() => this.connectedCallback());
return;
}
this._rendered = true;
this.classList.add("rendered-title");
const source = (this.textContent || "").trim();
+2
View File
@@ -10,4 +10,6 @@ import "./AppDialog.js";
import "./AppContextMenu.js";
import "./AppLightbox.js";
import "./AppDocsChat.js";
import "./AppIsslop.js";
import "./AppIsslopRun.js";
import "./ContainerTerminal.js";