sriharsha-cr commited on
Commit
77105ca
Β·
1 Parent(s): 183208c
Files changed (8) hide show
  1. .dockerignore +12 -0
  2. Dockerfile +32 -0
  3. app.py +3 -6
  4. config.py +0 -3
  5. db/store.py +15 -75
  6. docker-compose.yml +19 -0
  7. ui/compress_tab.py +32 -29
  8. ui/history_tab.py +27 -18
.dockerignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .venv
3
+ .env
4
+ __pycache__
5
+ *.pyc
6
+ *.db
7
+ .claude
8
+ CLAUDE.md
9
+ AGENTS.md
10
+ my-notes
11
+ docs
12
+ *.ipynb
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies required by torch and transformers
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ build-essential \
8
+ git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install CPU-only torch first to keep image lean, then remaining deps
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir torch>=2.2.0 --index-url https://download.pytorch.org/whl/cpu \
14
+ && pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy application code
17
+ COPY app.py config.py ./
18
+ COPY core/ core/
19
+ COPY db/ db/
20
+ COPY models/ models/
21
+ COPY ui/ ui/
22
+
23
+ # HuggingFace model cache is stored under /data so it can be mounted as a
24
+ # volume and downloaded weights survive container restarts.
25
+ ENV HF_HOME=/data/hf_cache \
26
+ PORT=7860
27
+
28
+ VOLUME /data
29
+
30
+ EXPOSE 7860
31
+
32
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import gradio as gr
2
  import config
3
- from db.store import init_db
4
  from models.model_loader import get_llm, get_embedder
5
  from ui.compress_tab import build_compress_tab
6
  from ui.history_tab import build_history_tab
@@ -8,15 +7,13 @@ from ui.history_tab import build_history_tab
8
 
9
  def build_app() -> gr.Blocks:
10
  with gr.Blocks(title=config.APP_TITLE) as app:
11
- build_compress_tab()
12
- build_history_tab()
 
13
  return app
14
 
15
 
16
  if __name__ == "__main__":
17
- print("Initialising database...")
18
- init_db()
19
-
20
  print("Loading models (first run may download weights)...")
21
  get_llm()
22
  get_embedder()
 
1
  import gradio as gr
2
  import config
 
3
  from models.model_loader import get_llm, get_embedder
4
  from ui.compress_tab import build_compress_tab
5
  from ui.history_tab import build_history_tab
 
7
 
8
  def build_app() -> gr.Blocks:
9
  with gr.Blocks(title=config.APP_TITLE) as app:
10
+ run_store = gr.State(value=[])
11
+ build_compress_tab(run_store)
12
+ build_history_tab(run_store)
13
  return app
14
 
15
 
16
  if __name__ == "__main__":
 
 
 
17
  print("Loading models (first run may download weights)...")
18
  get_llm()
19
  get_embedder()
config.py CHANGED
@@ -60,9 +60,6 @@ EMBEDDER_INFO = {
60
  DEFAULT_TARGET_TOKENS = 500
61
  MAX_NEW_TOKENS = 1024
62
 
63
- # Database
64
- DB_PATH = os.getenv("DB_PATH", "tinypress.db")
65
-
66
  # Gradio
67
  APP_TITLE = "TinyPress"
68
  SERVER_PORT = int(os.getenv("PORT", 7860))
 
60
  DEFAULT_TARGET_TOKENS = 500
61
  MAX_NEW_TOKENS = 1024
62
 
 
 
 
63
  # Gradio
64
  APP_TITLE = "TinyPress"
65
  SERVER_PORT = int(os.getenv("PORT", 7860))
db/store.py CHANGED
@@ -1,87 +1,27 @@
1
- import sqlite3
2
- import config
3
- from pathlib import Path
4
 
5
 
6
- def _connect():
7
- conn = sqlite3.connect(config.DB_PATH)
8
- conn.row_factory = sqlite3.Row
9
- return conn
10
 
11
 
12
- def init_db():
13
- schema = Path(__file__).parent / "schema.sql"
14
- conn = _connect()
15
- conn.executescript(schema.read_text())
16
- # Migrate existing databases that pre-date new columns.
17
- for col, typedef in [("tokenizer", "TEXT NOT NULL DEFAULT ''"), ("duration_ms", "REAL NOT NULL DEFAULT 0"), ("feedback", "INTEGER"), ("feedback_comment", "TEXT")]:
18
- try:
19
- conn.execute(f"ALTER TABLE compression_runs ADD COLUMN {col} {typedef}")
20
- except sqlite3.OperationalError:
21
- pass # column already exists
22
- conn.commit()
23
- conn.close()
24
 
25
 
26
- def save_run(record: dict) -> int:
27
- conn = _connect()
28
- cursor = conn.execute(
29
- """
30
- INSERT INTO compression_runs
31
- (timestamp, model, tokenizer, input_tokens, output_tokens, target_tokens,
32
- compression_ratio, quality_score, duration_ms, input_text, output_text)
33
- VALUES
34
- (:timestamp, :model, :tokenizer, :input_tokens, :output_tokens, :target_tokens,
35
- :compression_ratio, :quality_score, :duration_ms, :input_text, :output_text)
36
- """,
37
- record,
38
- )
39
- run_id = cursor.lastrowid
40
- conn.commit()
41
- conn.close()
42
- return run_id
43
 
44
 
45
- def update_feedback(run_id: int, value: int):
46
- conn = _connect()
47
- conn.execute(
48
- "UPDATE compression_runs SET feedback = ? WHERE id = ?",
49
- (value, run_id),
50
- )
51
- conn.commit()
52
- conn.close()
53
 
54
 
55
- def update_feedback_comment(run_id: int, comment: str):
56
- conn = _connect()
57
- conn.execute(
58
- "UPDATE compression_runs SET feedback_comment = ? WHERE id = ?",
59
- (comment, run_id),
60
- )
61
- conn.commit()
62
- conn.close()
63
 
64
 
65
- def delete_run(run_id: int):
66
- conn = _connect()
67
- conn.execute("DELETE FROM compression_runs WHERE id = ?", (run_id,))
68
- conn.commit()
69
- conn.close()
70
-
71
-
72
- def get_run(run_id: int) -> dict | None:
73
- conn = _connect()
74
- row = conn.execute(
75
- "SELECT * FROM compression_runs WHERE id = ?", (run_id,)
76
- ).fetchone()
77
- conn.close()
78
- return dict(row) if row else None
79
-
80
-
81
- def get_runs(limit: int = 100) -> list[dict]:
82
- conn = _connect()
83
- rows = conn.execute(
84
- "SELECT * FROM compression_runs ORDER BY id DESC LIMIT ?", (limit,)
85
- ).fetchall()
86
- conn.close()
87
- return [dict(r) for r in rows]
 
1
+ def make_store() -> list:
2
+ return []
 
3
 
4
 
5
+ def save_run(store: list, record: dict) -> tuple[int, list]:
6
+ run_id = store[-1]["id"] + 1 if store else 1
7
+ return run_id, store + [{"id": run_id, **record}]
 
8
 
9
 
10
+ def get_runs(store: list, limit: int = 100) -> list[dict]:
11
+ return list(reversed(store))[:limit]
 
 
 
 
 
 
 
 
 
 
12
 
13
 
14
+ def get_run(store: list, run_id: int) -> dict | None:
15
+ return next((r for r in store if r["id"] == run_id), None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
 
18
+ def delete_run(store: list, run_id: int) -> list:
19
+ return [r for r in store if r["id"] != run_id]
 
 
 
 
 
 
20
 
21
 
22
+ def update_feedback(store: list, run_id: int, value: int) -> list:
23
+ return [{**r, "feedback": value} if r["id"] == run_id else r for r in store]
 
 
 
 
 
 
24
 
25
 
26
+ def update_feedback_comment(store: list, run_id: int, comment: str) -> list:
27
+ return [{**r, "feedback_comment": comment} if r["id"] == run_id else r for r in store]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docker-compose.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ networks:
2
+ hf-build-small:
3
+ driver: bridge
4
+
5
+ volumes:
6
+ tinypress-data:
7
+
8
+ services:
9
+ tinypress:
10
+ build: .
11
+ ports:
12
+ - "7860:7860"
13
+ volumes:
14
+ - tinypress-data:/data
15
+ env_file:
16
+ - .env
17
+ networks:
18
+ - hf-build-small
19
+ restart: unless-stopped
ui/compress_tab.py CHANGED
@@ -42,7 +42,6 @@ def _render_token_html(text: str) -> str:
42
  spans = []
43
  for i, tok in enumerate(tokens):
44
  color = _PALETTE[i % len(_PALETTE)]
45
- # Make leading whitespace visible with a mid-dot; escape everything else.
46
  display = _h.escape(tok).replace(
47
  " ", '<span style="opacity:0.35;font-size:0.7em">Β·</span>'
48
  )
@@ -112,12 +111,13 @@ def compression_status(text: str, target_tokens: int) -> str:
112
 
113
  # ── core handlers ─────────────────────────────────────────────────────────────
114
 
115
- def run_compression(text: str, target_tokens: int):
116
  _hidden = gr.update(visible=False)
117
  if not text.strip():
118
  return ("", 0, 0, 0, 0.0, None,
119
  _hidden, _hidden, gr.update(value="", visible=False),
120
- gr.update(value="", visible=False), _hidden, gr.update(value="", visible=False))
 
121
 
122
  t0 = time.perf_counter()
123
  compressed, input_tokens, output_tokens = compress(text, int(target_tokens))
@@ -126,7 +126,7 @@ def run_compression(text: str, target_tokens: int):
126
  ratio = round(output_tokens / input_tokens, 4) if input_tokens else 0.0
127
  quality = semantic_score(text, compressed)
128
 
129
- run_id = save_run({
130
  "timestamp": datetime.now(timezone.utc).isoformat(),
131
  "model": get_current_model_id() or config.LLM_MODEL,
132
  "tokenizer": get_current_tokenizer_id() or config.LLM_MODEL,
@@ -143,11 +143,12 @@ def run_compression(text: str, target_tokens: int):
143
  return (
144
  compressed, input_tokens, output_tokens, ratio, quality,
145
  run_id,
146
- gr.update(visible=True), gr.update(visible=True), # thumbs buttons
147
- gr.update(value="", visible=True), # feedback_status
148
- gr.update(value="", visible=False), # comment_box reset
149
- gr.update(visible=False), # save_comment_btn reset
150
- gr.update(value="", visible=False), # comment_saved reset
 
151
  )
152
 
153
 
@@ -173,26 +174,28 @@ def on_embedder_change(model_id: str) -> str:
173
  return config.EMBEDDER_INFO.get(model_id, "")
174
 
175
 
176
- def submit_feedback(run_id, value: int):
177
  if run_id is None:
178
- return "Run a compression first.", gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=False)
179
- update_feedback(run_id, value)
 
 
180
  msg = "πŸ‘ Marked as helpful β€” thanks!" if value == 1 else "πŸ‘Ž Noted β€” thanks for the feedback!"
181
- return msg, gr.update(visible=True), gr.update(visible=True), gr.update(value="", visible=False)
182
 
183
 
184
- def save_comment(run_id, comment: str):
185
  if run_id is None:
186
- return gr.update(value="Run a compression first.", visible=True)
187
  if not comment.strip():
188
- return gr.update(value="Type a note first.", visible=True)
189
- update_feedback_comment(run_id, comment.strip())
190
- return gr.update(value="βœ“ Note saved.", visible=True)
191
 
192
 
193
  # ── UI ────────────────────────────────────────────────────────────────────────
194
 
195
- def build_compress_tab() -> gr.Tab:
196
  with gr.Tab("Compress") as tab:
197
  gr.Markdown("## TinyPress β€” Prompt Compression Engine")
198
  gr.Markdown(
@@ -303,25 +306,25 @@ def build_compress_tab() -> gr.Tab:
303
  load_embedder_btn.click(fn=load_embedder, inputs=[embedder_dropdown], outputs=[embedder_status])
304
  compress_btn.click(
305
  fn=run_compression,
306
- inputs=[input_text, target_slider],
307
  outputs=[output_text, input_tok, output_tok, ratio, quality,
308
  last_run_id, thumbs_up_btn, thumbs_down_btn, feedback_status,
309
- comment_box, save_comment_btn, comment_saved],
310
  )
311
  thumbs_up_btn.click(
312
- fn=lambda run_id: submit_feedback(run_id, 1),
313
- inputs=[last_run_id],
314
- outputs=[feedback_status, comment_box, save_comment_btn, comment_saved],
315
  )
316
  thumbs_down_btn.click(
317
- fn=lambda run_id: submit_feedback(run_id, -1),
318
- inputs=[last_run_id],
319
- outputs=[feedback_status, comment_box, save_comment_btn, comment_saved],
320
  )
321
  save_comment_btn.click(
322
  fn=save_comment,
323
- inputs=[last_run_id, comment_box],
324
- outputs=[comment_saved],
325
  )
326
 
327
  return tab
 
42
  spans = []
43
  for i, tok in enumerate(tokens):
44
  color = _PALETTE[i % len(_PALETTE)]
 
45
  display = _h.escape(tok).replace(
46
  " ", '<span style="opacity:0.35;font-size:0.7em">Β·</span>'
47
  )
 
111
 
112
  # ── core handlers ─────────────────────────────────────────────────────────────
113
 
114
+ def run_compression(text: str, target_tokens: int, run_store: list):
115
  _hidden = gr.update(visible=False)
116
  if not text.strip():
117
  return ("", 0, 0, 0, 0.0, None,
118
  _hidden, _hidden, gr.update(value="", visible=False),
119
+ gr.update(value="", visible=False), _hidden, gr.update(value="", visible=False),
120
+ run_store)
121
 
122
  t0 = time.perf_counter()
123
  compressed, input_tokens, output_tokens = compress(text, int(target_tokens))
 
126
  ratio = round(output_tokens / input_tokens, 4) if input_tokens else 0.0
127
  quality = semantic_score(text, compressed)
128
 
129
+ run_id, new_store = save_run(run_store, {
130
  "timestamp": datetime.now(timezone.utc).isoformat(),
131
  "model": get_current_model_id() or config.LLM_MODEL,
132
  "tokenizer": get_current_tokenizer_id() or config.LLM_MODEL,
 
143
  return (
144
  compressed, input_tokens, output_tokens, ratio, quality,
145
  run_id,
146
+ gr.update(visible=True), gr.update(visible=True),
147
+ gr.update(value="", visible=True),
148
+ gr.update(value="", visible=False),
149
+ gr.update(visible=False),
150
+ gr.update(value="", visible=False),
151
+ new_store,
152
  )
153
 
154
 
 
174
  return config.EMBEDDER_INFO.get(model_id, "")
175
 
176
 
177
+ def submit_feedback(run_id, value: int, run_store: list):
178
  if run_id is None:
179
+ return ("Run a compression first.",
180
+ gr.update(visible=False), gr.update(visible=False),
181
+ gr.update(value="", visible=False), run_store)
182
+ new_store = update_feedback(run_store, run_id, value)
183
  msg = "πŸ‘ Marked as helpful β€” thanks!" if value == 1 else "πŸ‘Ž Noted β€” thanks for the feedback!"
184
+ return msg, gr.update(visible=True), gr.update(visible=True), gr.update(value="", visible=False), new_store
185
 
186
 
187
+ def save_comment(run_id, comment: str, run_store: list):
188
  if run_id is None:
189
+ return gr.update(value="Run a compression first.", visible=True), run_store
190
  if not comment.strip():
191
+ return gr.update(value="Type a note first.", visible=True), run_store
192
+ new_store = update_feedback_comment(run_store, run_id, comment.strip())
193
+ return gr.update(value="βœ“ Note saved.", visible=True), new_store
194
 
195
 
196
  # ── UI ────────────────────────────────────────────────────────────────────────
197
 
198
+ def build_compress_tab(run_store) -> gr.Tab:
199
  with gr.Tab("Compress") as tab:
200
  gr.Markdown("## TinyPress β€” Prompt Compression Engine")
201
  gr.Markdown(
 
306
  load_embedder_btn.click(fn=load_embedder, inputs=[embedder_dropdown], outputs=[embedder_status])
307
  compress_btn.click(
308
  fn=run_compression,
309
+ inputs=[input_text, target_slider, run_store],
310
  outputs=[output_text, input_tok, output_tok, ratio, quality,
311
  last_run_id, thumbs_up_btn, thumbs_down_btn, feedback_status,
312
+ comment_box, save_comment_btn, comment_saved, run_store],
313
  )
314
  thumbs_up_btn.click(
315
+ fn=lambda run_id, store: submit_feedback(run_id, 1, store),
316
+ inputs=[last_run_id, run_store],
317
+ outputs=[feedback_status, comment_box, save_comment_btn, comment_saved, run_store],
318
  )
319
  thumbs_down_btn.click(
320
+ fn=lambda run_id, store: submit_feedback(run_id, -1, store),
321
+ inputs=[last_run_id, run_store],
322
+ outputs=[feedback_status, comment_box, save_comment_btn, comment_saved, run_store],
323
  )
324
  save_comment_btn.click(
325
  fn=save_comment,
326
+ inputs=[last_run_id, comment_box, run_store],
327
+ outputs=[comment_saved, run_store],
328
  )
329
 
330
  return tab
ui/history_tab.py CHANGED
@@ -11,10 +11,18 @@ _ALL_COLS = [
11
  "feedback", "feedback_comment",
12
  ]
13
 
 
 
 
 
 
 
 
14
 
15
- def load_history(selected_cols=None):
 
16
  cols = selected_cols if selected_cols else _DEFAULT_COLS
17
- runs = get_runs(limit=100)
18
  if not runs:
19
  return pd.DataFrame(columns=cols), "", "", ""
20
  df = pd.DataFrame(runs)
@@ -25,30 +33,31 @@ def load_history(selected_cols=None):
25
  return df, avg_quality, avg_ratio, ""
26
 
27
 
28
- def on_row_select(evt: gr.SelectData, df: pd.DataFrame):
29
  if df is None or df.empty:
30
  return None, "", "No rows available."
31
  row_idx = evt.index[0]
32
  run_id = int(df.iloc[row_idx]["id"])
33
- record = get_run(run_id)
34
  if not record:
35
- return None, "", f"Row {run_id} not found in database."
36
  diff_html = render_diff_html(record)
37
  return run_id, diff_html, f"Row {run_id} selected β€” click Delete to remove."
38
 
39
 
40
- def delete_selected(run_id, selected_cols):
41
  if run_id is None:
42
- df, avg_q, avg_r, _ = load_history(selected_cols)
43
- return df, avg_q, avg_r, None, "", "No row selected."
44
- delete_run(run_id)
45
- df, avg_q, avg_r, _ = load_history(selected_cols)
46
- return df, avg_q, avg_r, None, "", f"Row {run_id} deleted."
47
 
48
 
49
- def build_history_tab() -> gr.Tab:
50
  with gr.Tab("History") as tab:
51
  gr.Markdown("## Compression Run History")
 
52
 
53
  with gr.Row():
54
  refresh_btn = gr.Button("Refresh", variant="secondary")
@@ -79,18 +88,18 @@ def build_history_tab() -> gr.Tab:
79
 
80
  _outputs = [history_table, avg_quality, avg_ratio, diff_panel]
81
 
82
- refresh_btn.click(fn=load_history, inputs=[col_picker], outputs=_outputs)
83
- tab.select(fn=load_history, inputs=[col_picker], outputs=_outputs)
84
- col_picker.change(fn=load_history, inputs=[col_picker], outputs=_outputs)
85
  history_table.select(
86
  fn=on_row_select,
87
- inputs=[history_table],
88
  outputs=[selected_id, diff_panel, delete_status],
89
  )
90
  delete_btn.click(
91
  fn=delete_selected,
92
- inputs=[selected_id, col_picker],
93
- outputs=[history_table, avg_quality, avg_ratio, selected_id, diff_panel, delete_status],
94
  )
95
 
96
  return tab
 
11
  "feedback", "feedback_comment",
12
  ]
13
 
14
+ _SESSION_WARNING = (
15
+ '<div style="background:#fef9c3;border:1px solid #eab308;color:#854d0e;'
16
+ 'padding:8px 12px;border-radius:6px;font-size:0.9rem;margin-bottom:4px">'
17
+ '⚠️ <strong>Session only</strong> β€” history is stored in memory and will be '
18
+ 'cleared when you close or refresh this page. No data is persisted to disk.'
19
+ '</div>'
20
+ )
21
 
22
+
23
+ def load_history(selected_cols, run_store):
24
  cols = selected_cols if selected_cols else _DEFAULT_COLS
25
+ runs = get_runs(run_store, limit=100)
26
  if not runs:
27
  return pd.DataFrame(columns=cols), "", "", ""
28
  df = pd.DataFrame(runs)
 
33
  return df, avg_quality, avg_ratio, ""
34
 
35
 
36
+ def on_row_select(evt: gr.SelectData, df: pd.DataFrame, run_store: list):
37
  if df is None or df.empty:
38
  return None, "", "No rows available."
39
  row_idx = evt.index[0]
40
  run_id = int(df.iloc[row_idx]["id"])
41
+ record = get_run(run_store, run_id)
42
  if not record:
43
+ return None, "", f"Row {run_id} not found."
44
  diff_html = render_diff_html(record)
45
  return run_id, diff_html, f"Row {run_id} selected β€” click Delete to remove."
46
 
47
 
48
+ def delete_selected(run_id, selected_cols, run_store):
49
  if run_id is None:
50
+ df, avg_q, avg_r, _ = load_history(selected_cols, run_store)
51
+ return df, avg_q, avg_r, None, "", "No row selected.", run_store
52
+ new_store = delete_run(run_store, run_id)
53
+ df, avg_q, avg_r, _ = load_history(selected_cols, new_store)
54
+ return df, avg_q, avg_r, None, "", f"Row {run_id} deleted.", new_store
55
 
56
 
57
+ def build_history_tab(run_store) -> gr.Tab:
58
  with gr.Tab("History") as tab:
59
  gr.Markdown("## Compression Run History")
60
+ gr.HTML(_SESSION_WARNING)
61
 
62
  with gr.Row():
63
  refresh_btn = gr.Button("Refresh", variant="secondary")
 
88
 
89
  _outputs = [history_table, avg_quality, avg_ratio, diff_panel]
90
 
91
+ refresh_btn.click(fn=load_history, inputs=[col_picker, run_store], outputs=_outputs)
92
+ tab.select(fn=load_history, inputs=[col_picker, run_store], outputs=_outputs)
93
+ col_picker.change(fn=load_history, inputs=[col_picker, run_store], outputs=_outputs)
94
  history_table.select(
95
  fn=on_row_select,
96
+ inputs=[history_table, run_store],
97
  outputs=[selected_id, diff_panel, delete_status],
98
  )
99
  delete_btn.click(
100
  fn=delete_selected,
101
+ inputs=[selected_id, col_picker, run_store],
102
+ outputs=[history_table, avg_quality, avg_ratio, selected_id, diff_panel, delete_status, run_store],
103
  )
104
 
105
  return tab