Instructions to use moncefem/memory-lora-gemma4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use moncefem/memory-lora-gemma4 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Clean the model mirror
Browse filesRemoves ~11.6GB of weights that should not be published:
- sixview_v1/v2 checkpoints. sixview_v2 is the pre-standardisation head that
scored WORSE than applying no adapter (and worse than random noise); leaving
it downloadable invites someone to use it. Their metrics.jsonl stay for
provenance.
- duplicate all_lora/head.best.pt (same bytes as all_lora_best_cpt.pth) and a
stray h200_best/ copy.
- 49 files of snapshot_download bookkeeping under data/**/.cache/.
Keeps the two checkpoints worth having: all_lora_best_cpt.pth (merged corpus,
best) and h200_run/head.best.pt (prose-only, fallback).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +3 -0
- app/engine/generate_and_merge.py +27 -1
- app/engine/serve_fallback.py +37 -21
- app/src/app/api/jobs/[jobId]/route.ts +41 -0
- app/src/app/api/jobs/[jobId]/server/route.ts +118 -0
- app/src/app/compare/page.tsx +3 -268
- app/src/app/globals.css +17 -0
- app/src/app/page.tsx +321 -97
- app/src/app/serve/page.tsx +161 -0
- app/src/app/v1/[...path]/route.ts +12 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/.gitignore +0 -1
- data/real_code2lora/code2lora-evo/.cache/huggingface/CACHEDIR.TAG +0 -4
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/.gitattributes.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/README.md.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/train.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/ir_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/ir_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/train.parquet.metadata +0 -3
- data/real_code2lora/code2lora-evo/.cache/huggingface/trees/93a9e8240b9482092ecc768b804cc62bae1e875a.json +0 -69
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/.gitignore +0 -1
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/CACHEDIR.TAG +0 -4
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/.gitattributes.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/README.md.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/ir_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/ir_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/train.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/ir_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/ir_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/train.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static-anchor/.cache/huggingface/trees/65b7c77eed9398fc656052d058e9af7c2d432177.json +0 -83
- data/real_code2lora/code2lora-static/.cache/huggingface/.gitignore +0 -1
- data/real_code2lora/code2lora-static/.cache/huggingface/CACHEDIR.TAG +0 -4
- data/real_code2lora/code2lora-static/.cache/huggingface/download/.gitattributes.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/README.md.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/ir_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/ir_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/train.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/cr_test.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/cr_val.parquet.metadata +0 -3
- data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/train.parquet.metadata +0 -3
.gitignore
CHANGED
|
@@ -63,3 +63,6 @@ app/next-env.d.ts
|
|
| 63 |
# Local scratch training run (checkpoint rewritten every step — never commit)
|
| 64 |
runs/local_stdfix/
|
| 65 |
runs/stdfix_test/
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
# Local scratch training run (checkpoint rewritten every step — never commit)
|
| 64 |
runs/local_stdfix/
|
| 65 |
runs/stdfix_test/
|
| 66 |
+
|
| 67 |
+
# HF-mirror hygiene: snapshot_download bookkeeping, never useful to publish
|
| 68 |
+
data/**/.cache/
|
app/engine/generate_and_merge.py
CHANGED
|
@@ -45,8 +45,34 @@ _DTYPES = {"bfloat16": torch.bfloat16, "float16": torch.float16,
|
|
| 45 |
"float32": torch.float32}
|
| 46 |
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
def load_head(checkpoint: Path):
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
cfg = ckpt["config"]
|
| 51 |
head = MemoryLoRAHead(
|
| 52 |
input_dim=cfg["input_dim"],
|
|
|
|
| 45 |
"float32": torch.float32}
|
| 46 |
|
| 47 |
|
| 48 |
+
def _assert_real_checkpoint(path: Path) -> None:
|
| 49 |
+
"""Fail with an actionable message when the file is a git-LFS pointer.
|
| 50 |
+
|
| 51 |
+
A pointer is ~130 bytes of text starting "version https://git-lfs...".
|
| 52 |
+
Handed to torch.load it surfaces as `Unsupported operand 118` or
|
| 53 |
+
`invalid load key, 'v'` -- opcode 'v', the first byte of "version" -- which
|
| 54 |
+
reads like a corrupt checkpoint rather than a file that was never fetched.
|
| 55 |
+
Any `GIT_LFS_SKIP_SMUDGE=1` clone or pull leaves checkpoints in this state.
|
| 56 |
+
"""
|
| 57 |
+
if not path.exists():
|
| 58 |
+
raise FileNotFoundError(f"checkpoint not found: {path}")
|
| 59 |
+
if path.stat().st_size < 5000:
|
| 60 |
+
head = path.read_bytes()[:64]
|
| 61 |
+
if head.startswith(b"version https://git-lfs"):
|
| 62 |
+
raise RuntimeError(
|
| 63 |
+
f"{path} is a git-LFS pointer, not the checkpoint "
|
| 64 |
+
f"({path.stat().st_size} bytes). Fetch it with:\n"
|
| 65 |
+
f" git lfs pull --include=\"{path}\""
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
def load_head(checkpoint: Path):
|
| 70 |
+
_assert_real_checkpoint(Path(checkpoint))
|
| 71 |
+
# weights_only=False: these checkpoints carry the run's config/args dicts,
|
| 72 |
+
# not just tensors, and torch>=2.6 defaults the strict unpickler on --
|
| 73 |
+
# which rejects them ("Unsupported operand"). They are produced by this
|
| 74 |
+
# project's own training script, so loading them fully is intended.
|
| 75 |
+
ckpt = torch.load(checkpoint, map_location="cpu", weights_only=False)
|
| 76 |
cfg = ckpt["config"]
|
| 77 |
head = MemoryLoRAHead(
|
| 78 |
input_dim=cfg["input_dim"],
|
app/engine/serve_fallback.py
CHANGED
|
@@ -155,6 +155,16 @@ class Engine:
|
|
| 155 |
def stream(self, prompt: str, max_new_tokens: int, temperature: float,
|
| 156 |
top_p: float, stop: list[str] | None = None,
|
| 157 |
use_adapter: bool = True):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
from transformers import TextIteratorStreamer
|
| 159 |
enc = self.tok(prompt, return_tensors="pt").to(self.device)
|
| 160 |
streamer = TextIteratorStreamer(
|
|
@@ -167,29 +177,35 @@ class Engine:
|
|
| 167 |
pad_token_id=self.tok.pad_token_id or self.tok.eos_token_id,
|
| 168 |
streamer=streamer,
|
| 169 |
)
|
| 170 |
-
def _run():
|
| 171 |
-
with self._maybe_off(use_adapter):
|
| 172 |
-
self.model.generate(**kwargs)
|
| 173 |
-
threading.Thread(target=_run, daemon=True).start()
|
| 174 |
-
|
| 175 |
stops = self._stops(stop)
|
| 176 |
-
# A stop sequence can straddle two streamed pieces, so emit only the
|
| 177 |
-
# part of the buffer that can no longer become part of a stop string.
|
| 178 |
hold = max(len(s) for s in stops)
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
|
| 195 |
ENGINE: Engine | None = None
|
|
|
|
| 155 |
def stream(self, prompt: str, max_new_tokens: int, temperature: float,
|
| 156 |
top_p: float, stop: list[str] | None = None,
|
| 157 |
use_adapter: bool = True):
|
| 158 |
+
"""Stream tokens, with the adapter toggled for the WHOLE operation.
|
| 159 |
+
|
| 160 |
+
The enable/disable must wrap the entire generator -- both the worker
|
| 161 |
+
thread and the consumption of the streamer -- and the thread must be
|
| 162 |
+
joined before the context exits. PEFT's disable_adapter() flips state on
|
| 163 |
+
the shared model, so if the context closed while the next request was
|
| 164 |
+
already starting, that request would silently run with the wrong
|
| 165 |
+
adapter state. Putting the context inside the worker thread did exactly
|
| 166 |
+
that: base and adapted both came back as base.
|
| 167 |
+
"""
|
| 168 |
from transformers import TextIteratorStreamer
|
| 169 |
enc = self.tok(prompt, return_tensors="pt").to(self.device)
|
| 170 |
streamer = TextIteratorStreamer(
|
|
|
|
| 177 |
pad_token_id=self.tok.pad_token_id or self.tok.eos_token_id,
|
| 178 |
streamer=streamer,
|
| 179 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
stops = self._stops(stop)
|
|
|
|
|
|
|
| 181 |
hold = max(len(s) for s in stops)
|
| 182 |
+
|
| 183 |
+
with self._maybe_off(use_adapter):
|
| 184 |
+
worker = threading.Thread(
|
| 185 |
+
target=self.model.generate, kwargs=kwargs, daemon=True)
|
| 186 |
+
worker.start()
|
| 187 |
+
try:
|
| 188 |
+
# A stop sequence can straddle two streamed pieces, so emit only
|
| 189 |
+
# the part of the buffer that can no longer become part of one.
|
| 190 |
+
buf, emitted, hit = "", 0, False
|
| 191 |
+
for piece in streamer:
|
| 192 |
+
buf += piece
|
| 193 |
+
cut, hit = self._truncate_at_stop(buf, stops)
|
| 194 |
+
if hit:
|
| 195 |
+
if len(cut) > emitted:
|
| 196 |
+
yield cut[emitted:]
|
| 197 |
+
break
|
| 198 |
+
safe = max(0, len(buf) - hold)
|
| 199 |
+
if safe > emitted:
|
| 200 |
+
yield buf[emitted:safe]
|
| 201 |
+
emitted = safe
|
| 202 |
+
if not hit and len(buf) > emitted:
|
| 203 |
+
yield buf[emitted:]
|
| 204 |
+
finally:
|
| 205 |
+
# Drain and join so the model is idle before adapter state flips.
|
| 206 |
+
for _ in streamer:
|
| 207 |
+
pass
|
| 208 |
+
worker.join(timeout=120)
|
| 209 |
|
| 210 |
|
| 211 |
ENGINE: Engine | None = None
|
app/src/app/api/jobs/[jobId]/route.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { readStatus } from "@/lib/jobs";
|
| 3 |
+
import { rmSync, existsSync } from "node:fs";
|
| 4 |
+
import path from "node:path";
|
| 5 |
+
|
| 6 |
+
export const dynamic = "force-dynamic";
|
| 7 |
+
|
| 8 |
+
const WORKSPACES_DIR = path.join(process.cwd(), ".workspaces");
|
| 9 |
+
|
| 10 |
+
/** Delete a build: stop its inference server, then remove its workspace. */
|
| 11 |
+
export async function DELETE(
|
| 12 |
+
_req: NextRequest,
|
| 13 |
+
{ params }: { params: Promise<{ jobId: string }> }
|
| 14 |
+
) {
|
| 15 |
+
const { jobId } = await params;
|
| 16 |
+
// Reject anything that could escape the workspaces directory.
|
| 17 |
+
if (!/^[a-f0-9]{6,32}$/i.test(jobId)) {
|
| 18 |
+
return NextResponse.json({ error: "invalid job id" }, { status: 400 });
|
| 19 |
+
}
|
| 20 |
+
const dir = path.join(WORKSPACES_DIR, jobId);
|
| 21 |
+
if (!existsSync(dir)) {
|
| 22 |
+
return NextResponse.json({ error: "not found" }, { status: 404 });
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// Free the ~10GB the server holds before removing the adapter it loaded.
|
| 26 |
+
const status = readStatus(jobId);
|
| 27 |
+
const pid = status?.server?.pid;
|
| 28 |
+
if (pid) {
|
| 29 |
+
try {
|
| 30 |
+
process.kill(pid, "SIGTERM");
|
| 31 |
+
} catch {
|
| 32 |
+
/* already gone */
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
try {
|
| 36 |
+
rmSync(dir, { recursive: true, force: true });
|
| 37 |
+
} catch (e) {
|
| 38 |
+
return NextResponse.json({ error: String(e) }, { status: 500 });
|
| 39 |
+
}
|
| 40 |
+
return NextResponse.json({ ok: true, jobId });
|
| 41 |
+
}
|
app/src/app/api/jobs/[jobId]/server/route.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { readStatus } from "@/lib/jobs";
|
| 3 |
+
import { spawn } from "node:child_process";
|
| 4 |
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
| 5 |
+
import path from "node:path";
|
| 6 |
+
|
| 7 |
+
export const dynamic = "force-dynamic";
|
| 8 |
+
|
| 9 |
+
const APP_DIR = path.resolve(process.cwd());
|
| 10 |
+
const ENGINE_DIR = path.join(APP_DIR, "engine");
|
| 11 |
+
const WORKSPACES_DIR = path.join(APP_DIR, ".workspaces");
|
| 12 |
+
const PYTHON = process.env.MLORA_PYTHON || "python3";
|
| 13 |
+
|
| 14 |
+
function statusPath(jobId: string) {
|
| 15 |
+
return path.join(WORKSPACES_DIR, jobId, "status.json");
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Stop or restart a job's inference server.
|
| 20 |
+
*
|
| 21 |
+
* A transformers server that has been generating for a while slows down badly
|
| 22 |
+
* (MPS allocator churn, accumulated KV state), and the only reliable cure is a
|
| 23 |
+
* fresh process. Restarting reloads the same base+adapter, so the endpoint
|
| 24 |
+
* comes back on the same port with identical behaviour — no rebuild needed.
|
| 25 |
+
*/
|
| 26 |
+
export async function POST(
|
| 27 |
+
req: NextRequest,
|
| 28 |
+
{ params }: { params: Promise<{ jobId: string }> }
|
| 29 |
+
) {
|
| 30 |
+
const { jobId } = await params;
|
| 31 |
+
if (!/^[a-f0-9]{6,32}$/i.test(jobId)) {
|
| 32 |
+
return NextResponse.json({ error: "invalid job id" }, { status: 400 });
|
| 33 |
+
}
|
| 34 |
+
const dir = path.join(WORKSPACES_DIR, jobId);
|
| 35 |
+
if (!existsSync(dir)) {
|
| 36 |
+
return NextResponse.json({ error: "not found" }, { status: 404 });
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
let action = "restart";
|
| 40 |
+
try {
|
| 41 |
+
action = (await req.json())?.action || "restart";
|
| 42 |
+
} catch {
|
| 43 |
+
/* default */
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
const status = readStatus(jobId);
|
| 47 |
+
const oldPid = status?.server?.pid;
|
| 48 |
+
|
| 49 |
+
// Kill first in both cases — a restart must not leave two processes fighting
|
| 50 |
+
// over the same port.
|
| 51 |
+
if (oldPid) {
|
| 52 |
+
try {
|
| 53 |
+
process.kill(oldPid, "SIGTERM");
|
| 54 |
+
} catch {
|
| 55 |
+
/* already gone */
|
| 56 |
+
}
|
| 57 |
+
await new Promise((r) => setTimeout(r, 1200));
|
| 58 |
+
try {
|
| 59 |
+
process.kill(oldPid, 0);
|
| 60 |
+
process.kill(oldPid, "SIGKILL"); // still alive: force it
|
| 61 |
+
} catch {
|
| 62 |
+
/* exited cleanly */
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
const raw = existsSync(statusPath(jobId))
|
| 67 |
+
? JSON.parse(readFileSync(statusPath(jobId), "utf8"))
|
| 68 |
+
: {};
|
| 69 |
+
|
| 70 |
+
if (action === "stop") {
|
| 71 |
+
raw.state = "error";
|
| 72 |
+
raw.error = "server stopped — press Restart to bring it back";
|
| 73 |
+
raw.updated_at = Date.now() / 1000;
|
| 74 |
+
writeFileSync(statusPath(jobId), JSON.stringify(raw, null, 2));
|
| 75 |
+
return NextResponse.json({ ok: true, action: "stop" });
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
const port = status?.server?.port || 8000;
|
| 79 |
+
const adapter = path.join(dir, "adapter");
|
| 80 |
+
const merged = path.join(dir, "merged");
|
| 81 |
+
const args = [
|
| 82 |
+
path.join(ENGINE_DIR, "serve_fallback.py"),
|
| 83 |
+
"--port", String(port),
|
| 84 |
+
"--served-name", "memory-lora",
|
| 85 |
+
"--job", jobId,
|
| 86 |
+
];
|
| 87 |
+
if (existsSync(adapter)) {
|
| 88 |
+
args.push("--adapter", adapter);
|
| 89 |
+
} else if (existsSync(merged)) {
|
| 90 |
+
args.push("--model", merged);
|
| 91 |
+
} else {
|
| 92 |
+
return NextResponse.json(
|
| 93 |
+
{ error: "no adapter or merged model in this workspace" },
|
| 94 |
+
{ status: 409 }
|
| 95 |
+
);
|
| 96 |
+
}
|
| 97 |
+
if (process.env.MLORA_DEVICE) args.push("--device", process.env.MLORA_DEVICE);
|
| 98 |
+
|
| 99 |
+
const child = spawn(PYTHON, args, {
|
| 100 |
+
cwd: ENGINE_DIR,
|
| 101 |
+
detached: true,
|
| 102 |
+
stdio: "ignore",
|
| 103 |
+
env: { ...process.env, MLORA_WORKSPACES: WORKSPACES_DIR },
|
| 104 |
+
});
|
| 105 |
+
child.unref();
|
| 106 |
+
|
| 107 |
+
// serve_fallback --job rewrites status.json with its own pid once the model
|
| 108 |
+
// is loaded; mark it as loading meanwhile so the UI doesn't claim it's ready.
|
| 109 |
+
raw.state = "running";
|
| 110 |
+
raw.stage = "serve";
|
| 111 |
+
raw.error = null;
|
| 112 |
+
raw.server = { backend: "fallback", pid: child.pid, port, mode: "lora" };
|
| 113 |
+
raw.pipeline_pid = child.pid;
|
| 114 |
+
raw.updated_at = Date.now() / 1000;
|
| 115 |
+
writeFileSync(statusPath(jobId), JSON.stringify(raw, null, 2));
|
| 116 |
+
|
| 117 |
+
return NextResponse.json({ ok: true, action: "restart", pid: child.pid, port });
|
| 118 |
+
}
|
app/src/app/compare/page.tsx
CHANGED
|
@@ -1,270 +1,5 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
job_id: string;
|
| 7 |
-
repo_url: string;
|
| 8 |
-
stages: string[];
|
| 9 |
-
stage: string;
|
| 10 |
-
state: "running" | "ready" | "error";
|
| 11 |
-
error?: string | null;
|
| 12 |
-
server?: { backend: string; port: number; mode?: string };
|
| 13 |
-
};
|
| 14 |
-
|
| 15 |
-
type Turn = { role: "user" | "assistant"; base?: string; adapted?: string };
|
| 16 |
-
|
| 17 |
-
const STAGE_LABEL: Record<string, string> = {
|
| 18 |
-
clone: "clone",
|
| 19 |
-
embed: "embed",
|
| 20 |
-
model: "base model",
|
| 21 |
-
generate: "adapter",
|
| 22 |
-
merge: "merge",
|
| 23 |
-
serve: "serve",
|
| 24 |
-
ready: "ready",
|
| 25 |
-
};
|
| 26 |
-
|
| 27 |
-
// Questions that expose repo knowledge rather than general fluency — the base
|
| 28 |
-
// model can bluff a plausible answer to all of them, which is the point.
|
| 29 |
-
const SUGGESTED = [
|
| 30 |
-
"What is the core purpose of this repository?",
|
| 31 |
-
"What testing framework does this repository use?",
|
| 32 |
-
"How is this project built and packaged?",
|
| 33 |
-
"What are the main modules and how do they fit together?",
|
| 34 |
-
];
|
| 35 |
-
|
| 36 |
-
export default function Compare() {
|
| 37 |
-
const [url, setUrl] = useState("");
|
| 38 |
-
const [job, setJob] = useState<Job | null>(null);
|
| 39 |
-
const [jobs, setJobs] = useState<Job[]>([]);
|
| 40 |
-
const [turns, setTurns] = useState<Turn[]>([]);
|
| 41 |
-
const [q, setQ] = useState("");
|
| 42 |
-
const [busy, setBusy] = useState(false);
|
| 43 |
-
const scroller = useRef<HTMLDivElement>(null);
|
| 44 |
-
|
| 45 |
-
useEffect(() => {
|
| 46 |
-
const poll = async () => {
|
| 47 |
-
try {
|
| 48 |
-
const r = await fetch("/api/build");
|
| 49 |
-
const d = await r.json();
|
| 50 |
-
setJobs(d.jobs || []);
|
| 51 |
-
setJob((cur) =>
|
| 52 |
-
cur ? (d.jobs || []).find((j: Job) => j.job_id === cur.job_id) || cur : cur
|
| 53 |
-
);
|
| 54 |
-
} catch {
|
| 55 |
-
/* ignore */
|
| 56 |
-
}
|
| 57 |
-
};
|
| 58 |
-
poll();
|
| 59 |
-
const t = setInterval(poll, 2000);
|
| 60 |
-
return () => clearInterval(t);
|
| 61 |
-
}, []);
|
| 62 |
-
|
| 63 |
-
useEffect(() => {
|
| 64 |
-
scroller.current?.scrollTo({ top: scroller.current.scrollHeight, behavior: "smooth" });
|
| 65 |
-
}, [turns]);
|
| 66 |
-
|
| 67 |
-
const build = async () => {
|
| 68 |
-
if (!url.trim()) return;
|
| 69 |
-
setBusy(true);
|
| 70 |
-
try {
|
| 71 |
-
const r = await fetch("/api/build", {
|
| 72 |
-
method: "POST",
|
| 73 |
-
headers: { "content-type": "application/json" },
|
| 74 |
-
body: JSON.stringify({ repoUrl: url.trim() }),
|
| 75 |
-
});
|
| 76 |
-
const d = await r.json();
|
| 77 |
-
setJob({
|
| 78 |
-
job_id: d.jobId,
|
| 79 |
-
repo_url: url.trim(),
|
| 80 |
-
stages: ["clone", "embed", "model", "generate", "serve", "ready"],
|
| 81 |
-
stage: "clone",
|
| 82 |
-
state: "running",
|
| 83 |
-
});
|
| 84 |
-
setTurns([]);
|
| 85 |
-
setUrl("");
|
| 86 |
-
} finally {
|
| 87 |
-
setBusy(false);
|
| 88 |
-
}
|
| 89 |
-
};
|
| 90 |
-
|
| 91 |
-
// Both sides hit the same server; only the model id differs, so the
|
| 92 |
-
// comparison is the adapter and nothing else.
|
| 93 |
-
const askOne = async (model: string, question: string): Promise<string> => {
|
| 94 |
-
const r = await fetch("/v1/chat/completions", {
|
| 95 |
-
method: "POST",
|
| 96 |
-
headers: { "content-type": "application/json", "x-mlora-job": job!.job_id },
|
| 97 |
-
body: JSON.stringify({
|
| 98 |
-
model,
|
| 99 |
-
max_tokens: 160,
|
| 100 |
-
messages: [{ role: "user", content: question }],
|
| 101 |
-
}),
|
| 102 |
-
});
|
| 103 |
-
if (!r.ok) return `[error ${r.status}]`;
|
| 104 |
-
const d = await r.json();
|
| 105 |
-
return d.choices?.[0]?.message?.content?.trim() || "[empty]";
|
| 106 |
-
};
|
| 107 |
-
|
| 108 |
-
const ask = async (question?: string) => {
|
| 109 |
-
const text = (question ?? q).trim();
|
| 110 |
-
if (!text || !job || job.state !== "ready" || busy) return;
|
| 111 |
-
setBusy(true);
|
| 112 |
-
setQ("");
|
| 113 |
-
setTurns((t) => [...t, { role: "user", base: text }, { role: "assistant" }]);
|
| 114 |
-
try {
|
| 115 |
-
const [base, adapted] = await Promise.all([
|
| 116 |
-
askOne("base", text),
|
| 117 |
-
askOne(`memory-lora:${job.job_id}`, text),
|
| 118 |
-
]);
|
| 119 |
-
setTurns((t) => {
|
| 120 |
-
const c = [...t];
|
| 121 |
-
c[c.length - 1] = { role: "assistant", base, adapted };
|
| 122 |
-
return c;
|
| 123 |
-
});
|
| 124 |
-
} finally {
|
| 125 |
-
setBusy(false);
|
| 126 |
-
}
|
| 127 |
-
};
|
| 128 |
-
|
| 129 |
-
const ready = job?.state === "ready";
|
| 130 |
-
const idx = job ? job.stages?.indexOf(job.stage) ?? 0 : 0;
|
| 131 |
-
|
| 132 |
-
return (
|
| 133 |
-
<div className="cmp">
|
| 134 |
-
<header className="cmp-head">
|
| 135 |
-
<div>
|
| 136 |
-
<div className="eyebrow">Memory-LoRA · live comparison</div>
|
| 137 |
-
<h1>Same model. Same prompt. One has read your repo.</h1>
|
| 138 |
-
</div>
|
| 139 |
-
{job && (
|
| 140 |
-
<span className={`badge ${job.state}`}>
|
| 141 |
-
{job.state === "ready" ? "endpoint live" : job.state}
|
| 142 |
-
</span>
|
| 143 |
-
)}
|
| 144 |
-
</header>
|
| 145 |
-
|
| 146 |
-
<div className="bar">
|
| 147 |
-
<input
|
| 148 |
-
type="text"
|
| 149 |
-
placeholder="https://github.com/owner/repo"
|
| 150 |
-
value={url}
|
| 151 |
-
onChange={(e) => setUrl(e.target.value)}
|
| 152 |
-
onKeyDown={(e) => e.key === "Enter" && build()}
|
| 153 |
-
/>
|
| 154 |
-
<button onClick={build} disabled={busy || !url.trim()}>
|
| 155 |
-
{busy ? "Starting…" : "Build adapter"}
|
| 156 |
-
</button>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
{jobs.length > 0 && (
|
| 160 |
-
<div className="recent">
|
| 161 |
-
<span className="eyebrow">Ready</span>
|
| 162 |
-
{jobs
|
| 163 |
-
.filter((j) => j.state === "ready")
|
| 164 |
-
.slice(0, 4)
|
| 165 |
-
.map((j) => (
|
| 166 |
-
<button
|
| 167 |
-
key={j.job_id}
|
| 168 |
-
className={`chip ${job?.job_id === j.job_id ? "on" : ""}`}
|
| 169 |
-
onClick={() => {
|
| 170 |
-
setJob(j);
|
| 171 |
-
setTurns([]);
|
| 172 |
-
}}
|
| 173 |
-
>
|
| 174 |
-
{j.repo_url.replace("https://github.com/", "")}
|
| 175 |
-
</button>
|
| 176 |
-
))}
|
| 177 |
-
</div>
|
| 178 |
-
)}
|
| 179 |
-
|
| 180 |
-
{job && !ready && (
|
| 181 |
-
<div className="progress">
|
| 182 |
-
<div className="stages">
|
| 183 |
-
{(job.stages || []).map((s, i) => (
|
| 184 |
-
<span
|
| 185 |
-
key={s}
|
| 186 |
-
className={`stage ${i < idx ? "done" : ""} ${
|
| 187 |
-
i === idx && job.state === "running" ? "active" : ""
|
| 188 |
-
}`}
|
| 189 |
-
>
|
| 190 |
-
{STAGE_LABEL[s] || s}
|
| 191 |
-
</span>
|
| 192 |
-
))}
|
| 193 |
-
</div>
|
| 194 |
-
{job.state === "error" ? (
|
| 195 |
-
<p className="err">✗ {job.error}</p>
|
| 196 |
-
) : (
|
| 197 |
-
<p className="hint">
|
| 198 |
-
Cloning, embedding six views, and generating a LoRA adapter — about a minute.
|
| 199 |
-
</p>
|
| 200 |
-
)}
|
| 201 |
-
</div>
|
| 202 |
-
)}
|
| 203 |
-
|
| 204 |
-
{ready && (
|
| 205 |
-
<>
|
| 206 |
-
<div className="panes">
|
| 207 |
-
<div className="pane-head base">
|
| 208 |
-
<span className="dot" /> Frozen Gemma-4-E2B
|
| 209 |
-
<em>no repo knowledge</em>
|
| 210 |
-
</div>
|
| 211 |
-
<div className="pane-head adapted">
|
| 212 |
-
<span className="dot" /> + generated adapter
|
| 213 |
-
<em>{job!.repo_url.replace("https://github.com/", "")}</em>
|
| 214 |
-
</div>
|
| 215 |
-
</div>
|
| 216 |
-
|
| 217 |
-
<div className="scroll" ref={scroller}>
|
| 218 |
-
{turns.length === 0 && (
|
| 219 |
-
<div className="empty">
|
| 220 |
-
<p>Ask something only this repository can answer.</p>
|
| 221 |
-
<div className="suggest">
|
| 222 |
-
{SUGGESTED.map((s) => (
|
| 223 |
-
<button key={s} onClick={() => ask(s)} disabled={busy}>
|
| 224 |
-
{s}
|
| 225 |
-
</button>
|
| 226 |
-
))}
|
| 227 |
-
</div>
|
| 228 |
-
</div>
|
| 229 |
-
)}
|
| 230 |
-
{turns.map((t, i) =>
|
| 231 |
-
t.role === "user" ? (
|
| 232 |
-
<div key={i} className="q">
|
| 233 |
-
{t.base}
|
| 234 |
-
</div>
|
| 235 |
-
) : (
|
| 236 |
-
<div key={i} className="answers">
|
| 237 |
-
<div className="ans base">
|
| 238 |
-
{t.base ?? <span className="think">thinking…</span>}
|
| 239 |
-
</div>
|
| 240 |
-
<div className="ans adapted">
|
| 241 |
-
{t.adapted ?? <span className="think">thinking…</span>}
|
| 242 |
-
</div>
|
| 243 |
-
</div>
|
| 244 |
-
)
|
| 245 |
-
)}
|
| 246 |
-
</div>
|
| 247 |
-
|
| 248 |
-
<div className="ask">
|
| 249 |
-
<input
|
| 250 |
-
type="text"
|
| 251 |
-
placeholder="Ask about this repository…"
|
| 252 |
-
value={q}
|
| 253 |
-
onChange={(e) => setQ(e.target.value)}
|
| 254 |
-
onKeyDown={(e) => e.key === "Enter" && ask()}
|
| 255 |
-
disabled={busy}
|
| 256 |
-
/>
|
| 257 |
-
<button onClick={() => ask()} disabled={busy || !q.trim()}>
|
| 258 |
-
{busy ? "…" : "Ask both"}
|
| 259 |
-
</button>
|
| 260 |
-
</div>
|
| 261 |
-
<p className="foot">
|
| 262 |
-
Both answers come from one resident model on the same server — the only
|
| 263 |
-
difference is whether the generated adapter is switched on. No repository
|
| 264 |
-
text is in either prompt.
|
| 265 |
-
</p>
|
| 266 |
-
</>
|
| 267 |
-
)}
|
| 268 |
-
</div>
|
| 269 |
-
);
|
| 270 |
-
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
// The demo lives at "/" — this keeps "/compare" working too, so either URL
|
| 4 |
+
// lands on the side-by-side comparison instead of a 404.
|
| 5 |
+
export { default } from "../page";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/src/app/globals.css
CHANGED
|
@@ -162,3 +162,20 @@ label.small { color: var(--muted); font-size: 13px; }
|
|
| 162 |
border-radius: 10px; color: var(--text); padding: 13px 15px; font-size: 15px; }
|
| 163 |
.ask input:focus { outline: none; border-color: var(--accent); }
|
| 164 |
.foot { color: var(--muted); font-size: 12.5px; margin: 12px 0 0; text-align: center; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
border-radius: 10px; color: var(--text); padding: 13px 15px; font-size: 15px; }
|
| 163 |
.ask input:focus { outline: none; border-color: var(--accent); }
|
| 164 |
.foot { color: var(--muted); font-size: 12.5px; margin: 12px 0 0; text-align: center; }
|
| 165 |
+
.chip { display: inline-flex; align-items: center; gap: 0; padding: 0; overflow: hidden; }
|
| 166 |
+
.chip-main { background: none; border: none; color: inherit; font: inherit;
|
| 167 |
+
padding: 5px 6px 5px 12px; cursor: pointer; font-family: var(--mono); }
|
| 168 |
+
.chip-main em { font-style: normal; color: var(--muted); }
|
| 169 |
+
.chip-x { background: none; border: none; color: var(--muted); cursor: pointer;
|
| 170 |
+
font-size: 15px; line-height: 1; padding: 5px 10px 6px 4px; font-weight: 400; }
|
| 171 |
+
.chip-x:hover { color: var(--err); }
|
| 172 |
+
.clear-all { background: none; border: 1px solid var(--border); color: var(--muted);
|
| 173 |
+
border-radius: 999px; padding: 5px 12px; font-size: 12.5px; cursor: pointer;
|
| 174 |
+
font-weight: 400; }
|
| 175 |
+
.clear-all:hover { border-color: var(--err); color: var(--err); }
|
| 176 |
+
button.ghost { background: transparent; border: 1px solid var(--border);
|
| 177 |
+
color: var(--muted); font-weight: 400; }
|
| 178 |
+
button.ghost:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
|
| 179 |
+
.head-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
| 180 |
+
button.small { padding: 5px 12px; font-size: 12.5px; border-radius: 999px; }
|
| 181 |
+
button.ghost.danger:hover:not(:disabled) { border-color: var(--err); color: var(--err); }
|
app/src/app/page.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
-
import { useEffect, useState } from "react";
|
| 4 |
|
| 5 |
type Job = {
|
| 6 |
job_id: string;
|
|
@@ -9,10 +9,11 @@ type Job = {
|
|
| 9 |
stage: string;
|
| 10 |
state: "running" | "ready" | "error";
|
| 11 |
error?: string | null;
|
| 12 |
-
server?: { backend: string; port: number };
|
| 13 |
-
endpoint?: string;
|
| 14 |
};
|
| 15 |
|
|
|
|
|
|
|
| 16 |
const STAGE_LABEL: Record<string, string> = {
|
| 17 |
clone: "clone",
|
| 18 |
embed: "embed",
|
|
@@ -23,80 +24,37 @@ const STAGE_LABEL: Record<string, string> = {
|
|
| 23 |
ready: "ready",
|
| 24 |
};
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
export ANTHROPIC_API_KEY="local-demo" # any non-empty value
|
| 32 |
-
export ANTHROPIC_MODEL="${model}"
|
| 33 |
-
claude`;
|
| 34 |
-
const openai = `# vibe / aider / any OpenAI-compatible CLI
|
| 35 |
-
export OPENAI_BASE_URL="${base}/v1"
|
| 36 |
-
export OPENAI_API_KEY="local-demo"
|
| 37 |
-
export OPENAI_MODEL="${model}"
|
| 38 |
-
# e.g. aider --model openai/${model}
|
| 39 |
-
# e.g. vibe --base-url $OPENAI_BASE_URL --model ${model}`;
|
| 40 |
-
const curl = `curl ${base}/v1/chat/completions \\
|
| 41 |
-
-H "content-type: application/json" \\
|
| 42 |
-
-d '{"model":"${model}","messages":[{"role":"user","content":"What does this repo do?"}]}'`;
|
| 43 |
-
const text = tab === "claude" ? claude : tab === "openai" ? openai : curl;
|
| 44 |
-
return (
|
| 45 |
-
<div style={{ marginTop: 14 }}>
|
| 46 |
-
<div className="tabs">
|
| 47 |
-
<div className={`tab ${tab === "claude" ? "on" : ""}`} onClick={() => setTab("claude")}>Claude Code</div>
|
| 48 |
-
<div className={`tab ${tab === "openai" ? "on" : ""}`} onClick={() => setTab("openai")}>OpenAI CLIs</div>
|
| 49 |
-
<div className={`tab ${tab === "curl" ? "on" : ""}`} onClick={() => setTab("curl")}>curl</div>
|
| 50 |
-
</div>
|
| 51 |
-
<pre>{text}</pre>
|
| 52 |
-
<p className="hint">
|
| 53 |
-
Endpoint served by {job.server?.backend || "engine"} on port {job.server?.port}. The app
|
| 54 |
-
translates the Anthropic <code>/v1/messages</code> API to this model, so Claude Code works
|
| 55 |
-
directly.
|
| 56 |
-
</p>
|
| 57 |
-
</div>
|
| 58 |
-
);
|
| 59 |
-
}
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
<span className={`badge ${job.state}`}>{job.state}</span>
|
| 68 |
-
</div>
|
| 69 |
-
<div className="stages">
|
| 70 |
-
{(job.stages || []).map((s, i) => (
|
| 71 |
-
<span
|
| 72 |
-
key={s}
|
| 73 |
-
className={`stage ${i < idx || job.state === "ready" ? "done" : ""} ${
|
| 74 |
-
i === idx && job.state === "running" ? "active" : ""
|
| 75 |
-
}`}
|
| 76 |
-
>
|
| 77 |
-
{STAGE_LABEL[s] || s}
|
| 78 |
-
</span>
|
| 79 |
-
))}
|
| 80 |
-
</div>
|
| 81 |
-
{job.state === "error" && <div className="err">✗ {job.error}</div>}
|
| 82 |
-
{job.state === "ready" && <ConnectSnippets job={job} base={base} />}
|
| 83 |
-
</div>
|
| 84 |
-
);
|
| 85 |
-
}
|
| 86 |
|
| 87 |
-
export default function
|
| 88 |
const [url, setUrl] = useState("");
|
|
|
|
| 89 |
const [jobs, setJobs] = useState<Job[]>([]);
|
|
|
|
|
|
|
| 90 |
const [busy, setBusy] = useState(false);
|
| 91 |
-
const
|
| 92 |
|
| 93 |
useEffect(() => {
|
| 94 |
-
setBase(window.location.origin);
|
| 95 |
const poll = async () => {
|
| 96 |
try {
|
| 97 |
const r = await fetch("/api/build");
|
| 98 |
const d = await r.json();
|
| 99 |
setJobs(d.jobs || []);
|
|
|
|
|
|
|
|
|
|
| 100 |
} catch {
|
| 101 |
/* ignore */
|
| 102 |
}
|
|
@@ -106,56 +64,322 @@ export default function Home() {
|
|
| 106 |
return () => clearInterval(t);
|
| 107 |
}, []);
|
| 108 |
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
if (!url.trim()) return;
|
| 111 |
setBusy(true);
|
| 112 |
try {
|
| 113 |
-
await fetch("/api/build", {
|
| 114 |
method: "POST",
|
| 115 |
headers: { "content-type": "application/json" },
|
| 116 |
body: JSON.stringify({ repoUrl: url.trim() }),
|
| 117 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
setUrl("");
|
| 119 |
} finally {
|
| 120 |
setBusy(false);
|
| 121 |
}
|
| 122 |
};
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
return (
|
| 125 |
-
<div className="
|
| 126 |
-
<
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
merged into the base model and served behind an OpenAI + Anthropic compatible endpoint, so
|
| 131 |
-
your coding CLI talks to a model that already knows this repo.
|
| 132 |
-
</p>
|
| 133 |
-
|
| 134 |
-
<div className="card">
|
| 135 |
-
<div className="row">
|
| 136 |
-
<input
|
| 137 |
-
type="text"
|
| 138 |
-
placeholder="https://github.com/owner/repo"
|
| 139 |
-
value={url}
|
| 140 |
-
onChange={(e) => setUrl(e.target.value)}
|
| 141 |
-
onKeyDown={(e) => e.key === "Enter" && submit()}
|
| 142 |
-
/>
|
| 143 |
-
<button onClick={submit} disabled={busy}>
|
| 144 |
-
{busy ? "Starting…" : "Build & serve"}
|
| 145 |
-
</button>
|
| 146 |
</div>
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
</div>
|
| 152 |
|
| 153 |
-
{jobs.length
|
| 154 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
)}
|
| 156 |
-
{jobs.map((j) => (
|
| 157 |
-
<JobCard key={j.job_id} job={j} base={base} />
|
| 158 |
-
))}
|
| 159 |
</div>
|
| 160 |
);
|
| 161 |
}
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
+
import { useEffect, useRef, useState } from "react";
|
| 4 |
|
| 5 |
type Job = {
|
| 6 |
job_id: string;
|
|
|
|
| 9 |
stage: string;
|
| 10 |
state: "running" | "ready" | "error";
|
| 11 |
error?: string | null;
|
| 12 |
+
server?: { backend: string; port: number; mode?: string };
|
|
|
|
| 13 |
};
|
| 14 |
|
| 15 |
+
type Turn = { role: "user" | "assistant"; base?: string; adapted?: string };
|
| 16 |
+
|
| 17 |
const STAGE_LABEL: Record<string, string> = {
|
| 18 |
clone: "clone",
|
| 19 |
embed: "embed",
|
|
|
|
| 24 |
ready: "ready",
|
| 25 |
};
|
| 26 |
|
| 27 |
+
// Questions that expose repo knowledge rather than general fluency — the base
|
| 28 |
+
// model can bluff a plausible answer to all of them, which is the point.
|
| 29 |
+
// Short answers keep the demo snappy; the server's stop sequences cut the
|
| 30 |
+
// response at the end of the answer anyway, so this rarely truncates.
|
| 31 |
+
const MAX_TOKENS = 72;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
const SUGGESTED = [
|
| 34 |
+
"What is the core purpose of this repository?",
|
| 35 |
+
"What testing framework does this repository use?",
|
| 36 |
+
"How is this project built and packaged?",
|
| 37 |
+
"What are the main modules and how do they fit together?",
|
| 38 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
export default function Compare() {
|
| 41 |
const [url, setUrl] = useState("");
|
| 42 |
+
const [job, setJob] = useState<Job | null>(null);
|
| 43 |
const [jobs, setJobs] = useState<Job[]>([]);
|
| 44 |
+
const [turns, setTurns] = useState<Turn[]>([]);
|
| 45 |
+
const [q, setQ] = useState("");
|
| 46 |
const [busy, setBusy] = useState(false);
|
| 47 |
+
const scroller = useRef<HTMLDivElement>(null);
|
| 48 |
|
| 49 |
useEffect(() => {
|
|
|
|
| 50 |
const poll = async () => {
|
| 51 |
try {
|
| 52 |
const r = await fetch("/api/build");
|
| 53 |
const d = await r.json();
|
| 54 |
setJobs(d.jobs || []);
|
| 55 |
+
setJob((cur) =>
|
| 56 |
+
cur ? (d.jobs || []).find((j: Job) => j.job_id === cur.job_id) || cur : cur
|
| 57 |
+
);
|
| 58 |
} catch {
|
| 59 |
/* ignore */
|
| 60 |
}
|
|
|
|
| 64 |
return () => clearInterval(t);
|
| 65 |
}, []);
|
| 66 |
|
| 67 |
+
useEffect(() => {
|
| 68 |
+
scroller.current?.scrollTo({ top: scroller.current.scrollHeight, behavior: "smooth" });
|
| 69 |
+
}, [turns]);
|
| 70 |
+
|
| 71 |
+
const build = async () => {
|
| 72 |
if (!url.trim()) return;
|
| 73 |
setBusy(true);
|
| 74 |
try {
|
| 75 |
+
const r = await fetch("/api/build", {
|
| 76 |
method: "POST",
|
| 77 |
headers: { "content-type": "application/json" },
|
| 78 |
body: JSON.stringify({ repoUrl: url.trim() }),
|
| 79 |
});
|
| 80 |
+
const d = await r.json();
|
| 81 |
+
setJob({
|
| 82 |
+
job_id: d.jobId,
|
| 83 |
+
repo_url: url.trim(),
|
| 84 |
+
stages: ["clone", "embed", "model", "generate", "serve", "ready"],
|
| 85 |
+
stage: "clone",
|
| 86 |
+
state: "running",
|
| 87 |
+
});
|
| 88 |
+
setTurns([]);
|
| 89 |
setUrl("");
|
| 90 |
} finally {
|
| 91 |
setBusy(false);
|
| 92 |
}
|
| 93 |
};
|
| 94 |
|
| 95 |
+
// Both sides hit the same server; only the model id differs, so the
|
| 96 |
+
// comparison is the adapter and nothing else.
|
| 97 |
+
//
|
| 98 |
+
// Streamed, and deliberately sequential: one model serves both sides, so the
|
| 99 |
+
// server holds a lock and concurrent requests would queue anyway. Streaming
|
| 100 |
+
// is what makes it feel immediate -- tokens appear as they are produced
|
| 101 |
+
// instead of after the full answer.
|
| 102 |
+
const askOne = async (
|
| 103 |
+
model: string,
|
| 104 |
+
question: string,
|
| 105 |
+
onDelta: (chunk: string) => void
|
| 106 |
+
): Promise<void> => {
|
| 107 |
+
const r = await fetch("/v1/chat/completions", {
|
| 108 |
+
method: "POST",
|
| 109 |
+
headers: { "content-type": "application/json", "x-mlora-job": job!.job_id },
|
| 110 |
+
body: JSON.stringify({
|
| 111 |
+
model,
|
| 112 |
+
max_tokens: MAX_TOKENS,
|
| 113 |
+
stream: true,
|
| 114 |
+
messages: [{ role: "user", content: question }],
|
| 115 |
+
}),
|
| 116 |
+
});
|
| 117 |
+
if (!r.ok || !r.body) {
|
| 118 |
+
onDelta(`[error ${r.status}]`);
|
| 119 |
+
return;
|
| 120 |
+
}
|
| 121 |
+
const reader = r.body.getReader();
|
| 122 |
+
const dec = new TextDecoder();
|
| 123 |
+
let buf = "";
|
| 124 |
+
for (;;) {
|
| 125 |
+
const { done, value } = await reader.read();
|
| 126 |
+
if (done) break;
|
| 127 |
+
buf += dec.decode(value, { stream: true });
|
| 128 |
+
const lines = buf.split("\n");
|
| 129 |
+
buf = lines.pop() || "";
|
| 130 |
+
for (const line of lines) {
|
| 131 |
+
const t = line.trim();
|
| 132 |
+
if (!t.startsWith("data:")) continue;
|
| 133 |
+
const payload = t.slice(5).trim();
|
| 134 |
+
if (payload === "[DONE]") continue;
|
| 135 |
+
try {
|
| 136 |
+
const d = JSON.parse(payload);
|
| 137 |
+
const piece = d.choices?.[0]?.delta?.content;
|
| 138 |
+
if (piece) onDelta(piece);
|
| 139 |
+
} catch {
|
| 140 |
+
/* partial frame */
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
const ask = async (question?: string) => {
|
| 147 |
+
const text = (question ?? q).trim();
|
| 148 |
+
if (!text || !job || job.state !== "ready" || busy) return;
|
| 149 |
+
setBusy(true);
|
| 150 |
+
setQ("");
|
| 151 |
+
setTurns((t) => [
|
| 152 |
+
...t,
|
| 153 |
+
{ role: "user", base: text },
|
| 154 |
+
{ role: "assistant", base: "", adapted: "" },
|
| 155 |
+
]);
|
| 156 |
+
const push = (side: "base" | "adapted") => (chunk: string) =>
|
| 157 |
+
setTurns((t) => {
|
| 158 |
+
const c = [...t];
|
| 159 |
+
const last = { ...c[c.length - 1] };
|
| 160 |
+
last[side] = (last[side] || "") + chunk;
|
| 161 |
+
c[c.length - 1] = last;
|
| 162 |
+
return c;
|
| 163 |
+
});
|
| 164 |
+
try {
|
| 165 |
+
await askOne("base", text, push("base"));
|
| 166 |
+
await askOne(`memory-lora:${job.job_id}`, text, push("adapted"));
|
| 167 |
+
} finally {
|
| 168 |
+
setBusy(false);
|
| 169 |
+
}
|
| 170 |
+
};
|
| 171 |
+
|
| 172 |
+
const clearChat = () => setTurns([]);
|
| 173 |
+
|
| 174 |
+
// A long-lived transformers server degrades badly (allocator churn on MPS);
|
| 175 |
+
// a fresh process reloads the same base+adapter on the same port.
|
| 176 |
+
const serverAction = async (action: "stop" | "restart") => {
|
| 177 |
+
if (!job) return;
|
| 178 |
+
setBusy(true);
|
| 179 |
+
try {
|
| 180 |
+
await fetch(`/api/jobs/${job.job_id}/server`, {
|
| 181 |
+
method: "POST",
|
| 182 |
+
headers: { "content-type": "application/json" },
|
| 183 |
+
body: JSON.stringify({ action }),
|
| 184 |
+
});
|
| 185 |
+
const r = await fetch("/api/build");
|
| 186 |
+
setJobs((await r.json()).jobs || []);
|
| 187 |
+
} finally {
|
| 188 |
+
setBusy(false);
|
| 189 |
+
}
|
| 190 |
+
};
|
| 191 |
+
|
| 192 |
+
const deleteBuild = async (jobId: string) => {
|
| 193 |
+
await fetch(`/api/jobs/${jobId}`, { method: "DELETE" });
|
| 194 |
+
if (job?.job_id === jobId) {
|
| 195 |
+
setJob(null);
|
| 196 |
+
setTurns([]);
|
| 197 |
+
}
|
| 198 |
+
const r = await fetch("/api/build");
|
| 199 |
+
setJobs((await r.json()).jobs || []);
|
| 200 |
+
};
|
| 201 |
+
|
| 202 |
+
const clearAll = async () => {
|
| 203 |
+
await Promise.all(jobs.map((j) => fetch(`/api/jobs/${j.job_id}`, { method: "DELETE" })));
|
| 204 |
+
setJob(null);
|
| 205 |
+
setTurns([]);
|
| 206 |
+
setJobs([]);
|
| 207 |
+
};
|
| 208 |
+
|
| 209 |
+
const ready = job?.state === "ready";
|
| 210 |
+
const idx = job ? job.stages?.indexOf(job.stage) ?? 0 : 0;
|
| 211 |
+
|
| 212 |
return (
|
| 213 |
+
<div className="cmp">
|
| 214 |
+
<header className="cmp-head">
|
| 215 |
+
<div>
|
| 216 |
+
<div className="eyebrow">Memory-LoRA · live comparison</div>
|
| 217 |
+
<h1>Same model. Same prompt. One has read your repo.</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
</div>
|
| 219 |
+
{job && (
|
| 220 |
+
<div className="head-actions">
|
| 221 |
+
<span className={`badge ${job.state}`}>
|
| 222 |
+
{job.state === "ready" ? "endpoint live" : job.state}
|
| 223 |
+
</span>
|
| 224 |
+
<button
|
| 225 |
+
className="ghost small"
|
| 226 |
+
onClick={() => serverAction("restart")}
|
| 227 |
+
disabled={busy}
|
| 228 |
+
title="Kill and reload the model process — fixes a server that has become slow"
|
| 229 |
+
>
|
| 230 |
+
Restart model
|
| 231 |
+
</button>
|
| 232 |
+
<button
|
| 233 |
+
className="ghost small danger"
|
| 234 |
+
onClick={() => serverAction("stop")}
|
| 235 |
+
disabled={busy || job.state !== "ready"}
|
| 236 |
+
title="Kill the model process and free its memory"
|
| 237 |
+
>
|
| 238 |
+
Kill
|
| 239 |
+
</button>
|
| 240 |
+
</div>
|
| 241 |
+
)}
|
| 242 |
+
</header>
|
| 243 |
+
|
| 244 |
+
<div className="bar">
|
| 245 |
+
<input
|
| 246 |
+
type="text"
|
| 247 |
+
placeholder="https://github.com/owner/repo"
|
| 248 |
+
value={url}
|
| 249 |
+
onChange={(e) => setUrl(e.target.value)}
|
| 250 |
+
onKeyDown={(e) => e.key === "Enter" && build()}
|
| 251 |
+
/>
|
| 252 |
+
<button onClick={build} disabled={busy || !url.trim()}>
|
| 253 |
+
{busy ? "Starting…" : "Build adapter"}
|
| 254 |
+
</button>
|
| 255 |
</div>
|
| 256 |
|
| 257 |
+
{jobs.length > 0 && (
|
| 258 |
+
<div className="recent">
|
| 259 |
+
<span className="eyebrow">Ready</span>
|
| 260 |
+
{jobs.slice(0, 5).map((j) => (
|
| 261 |
+
<span key={j.job_id} className={`chip ${job?.job_id === j.job_id ? "on" : ""}`}>
|
| 262 |
+
<button
|
| 263 |
+
className="chip-main"
|
| 264 |
+
onClick={() => {
|
| 265 |
+
setJob(j);
|
| 266 |
+
setTurns([]);
|
| 267 |
+
}}
|
| 268 |
+
>
|
| 269 |
+
{j.repo_url.replace("https://github.com/", "")}
|
| 270 |
+
{j.state !== "ready" && <em> · {j.state}</em>}
|
| 271 |
+
</button>
|
| 272 |
+
<button
|
| 273 |
+
className="chip-x"
|
| 274 |
+
title="Delete this build"
|
| 275 |
+
aria-label={`Delete ${j.repo_url}`}
|
| 276 |
+
onClick={() => deleteBuild(j.job_id)}
|
| 277 |
+
>
|
| 278 |
+
×
|
| 279 |
+
</button>
|
| 280 |
+
</span>
|
| 281 |
+
))}
|
| 282 |
+
{jobs.length > 0 && (
|
| 283 |
+
<button className="clear-all" onClick={clearAll}>
|
| 284 |
+
Clear all builds
|
| 285 |
+
</button>
|
| 286 |
+
)}
|
| 287 |
+
</div>
|
| 288 |
+
)}
|
| 289 |
+
|
| 290 |
+
{job && !ready && (
|
| 291 |
+
<div className="progress">
|
| 292 |
+
<div className="stages">
|
| 293 |
+
{(job.stages || []).map((s, i) => (
|
| 294 |
+
<span
|
| 295 |
+
key={s}
|
| 296 |
+
className={`stage ${i < idx ? "done" : ""} ${
|
| 297 |
+
i === idx && job.state === "running" ? "active" : ""
|
| 298 |
+
}`}
|
| 299 |
+
>
|
| 300 |
+
{STAGE_LABEL[s] || s}
|
| 301 |
+
</span>
|
| 302 |
+
))}
|
| 303 |
+
</div>
|
| 304 |
+
{job.state === "error" ? (
|
| 305 |
+
<p className="err">✗ {job.error}</p>
|
| 306 |
+
) : (
|
| 307 |
+
<p className="hint">
|
| 308 |
+
Cloning, embedding six views, and generating a LoRA adapter — about a minute.
|
| 309 |
+
</p>
|
| 310 |
+
)}
|
| 311 |
+
</div>
|
| 312 |
+
)}
|
| 313 |
+
|
| 314 |
+
{ready && (
|
| 315 |
+
<>
|
| 316 |
+
<div className="panes">
|
| 317 |
+
<div className="pane-head base">
|
| 318 |
+
<span className="dot" /> Frozen Gemma-4-E2B
|
| 319 |
+
<em>no repo knowledge</em>
|
| 320 |
+
</div>
|
| 321 |
+
<div className="pane-head adapted">
|
| 322 |
+
<span className="dot" /> + generated adapter
|
| 323 |
+
<em>{job!.repo_url.replace("https://github.com/", "")}</em>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div className="scroll" ref={scroller}>
|
| 328 |
+
{turns.length === 0 && (
|
| 329 |
+
<div className="empty">
|
| 330 |
+
<p>Ask something only this repository can answer.</p>
|
| 331 |
+
<div className="suggest">
|
| 332 |
+
{SUGGESTED.map((s) => (
|
| 333 |
+
<button key={s} onClick={() => ask(s)} disabled={busy}>
|
| 334 |
+
{s}
|
| 335 |
+
</button>
|
| 336 |
+
))}
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
)}
|
| 340 |
+
{turns.map((t, i) =>
|
| 341 |
+
t.role === "user" ? (
|
| 342 |
+
<div key={i} className="q">
|
| 343 |
+
{t.base}
|
| 344 |
+
</div>
|
| 345 |
+
) : (
|
| 346 |
+
<div key={i} className="answers">
|
| 347 |
+
<div className="ans base">
|
| 348 |
+
{t.base ? t.base : <span className="think">…</span>}
|
| 349 |
+
</div>
|
| 350 |
+
<div className="ans adapted">
|
| 351 |
+
{t.adapted ? t.adapted : <span className="think">…</span>}
|
| 352 |
+
</div>
|
| 353 |
+
</div>
|
| 354 |
+
)
|
| 355 |
+
)}
|
| 356 |
+
</div>
|
| 357 |
+
|
| 358 |
+
<div className="ask">
|
| 359 |
+
<input
|
| 360 |
+
type="text"
|
| 361 |
+
placeholder="Ask about this repository…"
|
| 362 |
+
value={q}
|
| 363 |
+
onChange={(e) => setQ(e.target.value)}
|
| 364 |
+
onKeyDown={(e) => e.key === "Enter" && ask()}
|
| 365 |
+
disabled={busy}
|
| 366 |
+
/>
|
| 367 |
+
<button onClick={() => ask()} disabled={busy || !q.trim()}>
|
| 368 |
+
{busy ? "…" : "Ask both"}
|
| 369 |
+
</button>
|
| 370 |
+
{turns.length > 0 && (
|
| 371 |
+
<button className="ghost" onClick={clearChat} disabled={busy}>
|
| 372 |
+
Clear chat
|
| 373 |
+
</button>
|
| 374 |
+
)}
|
| 375 |
+
</div>
|
| 376 |
+
<p className="foot">
|
| 377 |
+
Both answers come from one resident model on the same server — the only
|
| 378 |
+
difference is whether the generated adapter is switched on. No repository
|
| 379 |
+
text is in either prompt.
|
| 380 |
+
</p>
|
| 381 |
+
</>
|
| 382 |
)}
|
|
|
|
|
|
|
|
|
|
| 383 |
</div>
|
| 384 |
);
|
| 385 |
}
|
app/src/app/serve/page.tsx
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useEffect, useState } from "react";
|
| 4 |
+
|
| 5 |
+
type Job = {
|
| 6 |
+
job_id: string;
|
| 7 |
+
repo_url: string;
|
| 8 |
+
stages: string[];
|
| 9 |
+
stage: string;
|
| 10 |
+
state: "running" | "ready" | "error";
|
| 11 |
+
error?: string | null;
|
| 12 |
+
server?: { backend: string; port: number };
|
| 13 |
+
endpoint?: string;
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
const STAGE_LABEL: Record<string, string> = {
|
| 17 |
+
clone: "clone",
|
| 18 |
+
embed: "embed",
|
| 19 |
+
model: "base model",
|
| 20 |
+
generate: "adapter",
|
| 21 |
+
merge: "merge",
|
| 22 |
+
serve: "serve",
|
| 23 |
+
ready: "ready",
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
function ConnectSnippets({ job, base }: { job: Job; base: string }) {
|
| 27 |
+
const [tab, setTab] = useState<"claude" | "openai" | "curl">("claude");
|
| 28 |
+
const model = `memory-lora:${job.job_id}`;
|
| 29 |
+
const claude = `# Claude Code — point it at this repo-personalized model
|
| 30 |
+
export ANTHROPIC_BASE_URL="${base}"
|
| 31 |
+
export ANTHROPIC_API_KEY="local-demo" # any non-empty value
|
| 32 |
+
export ANTHROPIC_MODEL="${model}"
|
| 33 |
+
claude`;
|
| 34 |
+
const openai = `# vibe / aider / any OpenAI-compatible CLI
|
| 35 |
+
export OPENAI_BASE_URL="${base}/v1"
|
| 36 |
+
export OPENAI_API_KEY="local-demo"
|
| 37 |
+
export OPENAI_MODEL="${model}"
|
| 38 |
+
# e.g. aider --model openai/${model}
|
| 39 |
+
# e.g. vibe --base-url $OPENAI_BASE_URL --model ${model}`;
|
| 40 |
+
const curl = `curl ${base}/v1/chat/completions \\
|
| 41 |
+
-H "content-type: application/json" \\
|
| 42 |
+
-d '{"model":"${model}","messages":[{"role":"user","content":"What does this repo do?"}]}'`;
|
| 43 |
+
const text = tab === "claude" ? claude : tab === "openai" ? openai : curl;
|
| 44 |
+
return (
|
| 45 |
+
<div style={{ marginTop: 14 }}>
|
| 46 |
+
<div className="tabs">
|
| 47 |
+
<div className={`tab ${tab === "claude" ? "on" : ""}`} onClick={() => setTab("claude")}>Claude Code</div>
|
| 48 |
+
<div className={`tab ${tab === "openai" ? "on" : ""}`} onClick={() => setTab("openai")}>OpenAI CLIs</div>
|
| 49 |
+
<div className={`tab ${tab === "curl" ? "on" : ""}`} onClick={() => setTab("curl")}>curl</div>
|
| 50 |
+
</div>
|
| 51 |
+
<pre>{text}</pre>
|
| 52 |
+
<p className="hint">
|
| 53 |
+
Endpoint served by {job.server?.backend || "engine"} on port {job.server?.port}. The app
|
| 54 |
+
translates the Anthropic <code>/v1/messages</code> API to this model, so Claude Code works
|
| 55 |
+
directly.
|
| 56 |
+
</p>
|
| 57 |
+
</div>
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
function JobCard({ job, base }: { job: Job; base: string }) {
|
| 62 |
+
const idx = job.stages?.indexOf(job.stage) ?? 0;
|
| 63 |
+
return (
|
| 64 |
+
<div className="card">
|
| 65 |
+
<div className="jobhead">
|
| 66 |
+
<span className="repo">{job.repo_url}</span>
|
| 67 |
+
<span className={`badge ${job.state}`}>{job.state}</span>
|
| 68 |
+
</div>
|
| 69 |
+
<div className="stages">
|
| 70 |
+
{(job.stages || []).map((s, i) => (
|
| 71 |
+
<span
|
| 72 |
+
key={s}
|
| 73 |
+
className={`stage ${i < idx || job.state === "ready" ? "done" : ""} ${
|
| 74 |
+
i === idx && job.state === "running" ? "active" : ""
|
| 75 |
+
}`}
|
| 76 |
+
>
|
| 77 |
+
{STAGE_LABEL[s] || s}
|
| 78 |
+
</span>
|
| 79 |
+
))}
|
| 80 |
+
</div>
|
| 81 |
+
{job.state === "error" && <div className="err">✗ {job.error}</div>}
|
| 82 |
+
{job.state === "ready" && <ConnectSnippets job={job} base={base} />}
|
| 83 |
+
</div>
|
| 84 |
+
);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
export default function Home() {
|
| 88 |
+
const [url, setUrl] = useState("");
|
| 89 |
+
const [jobs, setJobs] = useState<Job[]>([]);
|
| 90 |
+
const [busy, setBusy] = useState(false);
|
| 91 |
+
const [base, setBase] = useState("");
|
| 92 |
+
|
| 93 |
+
useEffect(() => {
|
| 94 |
+
setBase(window.location.origin);
|
| 95 |
+
const poll = async () => {
|
| 96 |
+
try {
|
| 97 |
+
const r = await fetch("/api/build");
|
| 98 |
+
const d = await r.json();
|
| 99 |
+
setJobs(d.jobs || []);
|
| 100 |
+
} catch {
|
| 101 |
+
/* ignore */
|
| 102 |
+
}
|
| 103 |
+
};
|
| 104 |
+
poll();
|
| 105 |
+
const t = setInterval(poll, 2000);
|
| 106 |
+
return () => clearInterval(t);
|
| 107 |
+
}, []);
|
| 108 |
+
|
| 109 |
+
const submit = async () => {
|
| 110 |
+
if (!url.trim()) return;
|
| 111 |
+
setBusy(true);
|
| 112 |
+
try {
|
| 113 |
+
await fetch("/api/build", {
|
| 114 |
+
method: "POST",
|
| 115 |
+
headers: { "content-type": "application/json" },
|
| 116 |
+
body: JSON.stringify({ repoUrl: url.trim() }),
|
| 117 |
+
});
|
| 118 |
+
setUrl("");
|
| 119 |
+
} finally {
|
| 120 |
+
setBusy(false);
|
| 121 |
+
}
|
| 122 |
+
};
|
| 123 |
+
|
| 124 |
+
return (
|
| 125 |
+
<div className="wrap">
|
| 126 |
+
<h1>Memory-LoRA</h1>
|
| 127 |
+
<p className="sub">
|
| 128 |
+
Paste a git repository. A hypernetwork reads a 6-view embedding of the codebase and{" "}
|
| 129 |
+
<b>emits a LoRA adapter</b> for Gemma in one forward pass — no fine-tuning. The adapter is
|
| 130 |
+
merged into the base model and served behind an OpenAI + Anthropic compatible endpoint, so
|
| 131 |
+
your coding CLI talks to a model that already knows this repo.
|
| 132 |
+
</p>
|
| 133 |
+
|
| 134 |
+
<div className="card">
|
| 135 |
+
<div className="row">
|
| 136 |
+
<input
|
| 137 |
+
type="text"
|
| 138 |
+
placeholder="https://github.com/owner/repo"
|
| 139 |
+
value={url}
|
| 140 |
+
onChange={(e) => setUrl(e.target.value)}
|
| 141 |
+
onKeyDown={(e) => e.key === "Enter" && submit()}
|
| 142 |
+
/>
|
| 143 |
+
<button onClick={submit} disabled={busy}>
|
| 144 |
+
{busy ? "Starting…" : "Build & serve"}
|
| 145 |
+
</button>
|
| 146 |
+
</div>
|
| 147 |
+
<p className="hint">
|
| 148 |
+
First build downloads the base model (~10 GB) and takes a few minutes; later builds
|
| 149 |
+
reuse it.
|
| 150 |
+
</p>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
{jobs.length === 0 && (
|
| 154 |
+
<p className="hint">No builds yet. Paste a repo URL above to start one.</p>
|
| 155 |
+
)}
|
| 156 |
+
{jobs.map((j) => (
|
| 157 |
+
<JobCard key={j.job_id} job={j} base={base} />
|
| 158 |
+
))}
|
| 159 |
+
</div>
|
| 160 |
+
);
|
| 161 |
+
}
|
app/src/app/v1/[...path]/route.ts
CHANGED
|
@@ -11,6 +11,11 @@ export const dynamic = "force-dynamic";
|
|
| 11 |
const SERVED_MODEL = "memory-lora";
|
| 12 |
const API_KEY = process.env.MLORA_API_KEY || "";
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
function authOk(req: NextRequest): boolean {
|
| 15 |
if (!API_KEY) return true;
|
| 16 |
const auth = req.headers.get("authorization");
|
|
@@ -53,12 +58,15 @@ async function proxyPassthrough(
|
|
| 53 |
);
|
| 54 |
}
|
| 55 |
|
| 56 |
-
//
|
|
|
|
|
|
|
|
|
|
| 57 |
let body = rawBody;
|
| 58 |
if (rawBody) {
|
| 59 |
try {
|
| 60 |
const j = JSON.parse(rawBody);
|
| 61 |
-
j.model = SERVED_MODEL;
|
| 62 |
body = JSON.stringify(j);
|
| 63 |
} catch {
|
| 64 |
/* leave as-is */
|
|
@@ -96,7 +104,8 @@ async function handleMessages(req: NextRequest, rawBody: string): Promise<Respon
|
|
| 96 |
);
|
| 97 |
}
|
| 98 |
|
| 99 |
-
const oaiReq = anthropicToOpenAI(
|
|
|
|
| 100 |
const upstream = await fetch(`${target}/v1/chat/completions`, {
|
| 101 |
method: "POST",
|
| 102 |
headers: { "content-type": "application/json" },
|
|
|
|
| 11 |
const SERVED_MODEL = "memory-lora";
|
| 12 |
const API_KEY = process.env.MLORA_API_KEY || "";
|
| 13 |
|
| 14 |
+
/** True when the caller explicitly asked for the un-adapted base model. */
|
| 15 |
+
function isBaseModel(model: unknown): boolean {
|
| 16 |
+
return typeof model === "string" && model.trim().toLowerCase() === "base";
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
function authOk(req: NextRequest): boolean {
|
| 20 |
if (!API_KEY) return true;
|
| 21 |
const auth = req.headers.get("authorization");
|
|
|
|
| 58 |
);
|
| 59 |
}
|
| 60 |
|
| 61 |
+
// Normalize the model field so arbitrary client model strings work — but
|
| 62 |
+
// NEVER collapse "base" into the served name. The upstream server selects
|
| 63 |
+
// frozen-vs-adapted from this field, so rewriting it unconditionally made the
|
| 64 |
+
// side-by-side comparison silently return the adapted model on BOTH sides.
|
| 65 |
let body = rawBody;
|
| 66 |
if (rawBody) {
|
| 67 |
try {
|
| 68 |
const j = JSON.parse(rawBody);
|
| 69 |
+
if (!isBaseModel(j.model)) j.model = SERVED_MODEL;
|
| 70 |
body = JSON.stringify(j);
|
| 71 |
} catch {
|
| 72 |
/* leave as-is */
|
|
|
|
| 104 |
);
|
| 105 |
}
|
| 106 |
|
| 107 |
+
const oaiReq = anthropicToOpenAI(
|
| 108 |
+
body, isBaseModel(body.model) ? "base" : SERVED_MODEL);
|
| 109 |
const upstream = await fetch(`${target}/v1/chat/completions`, {
|
| 110 |
method: "POST",
|
| 111 |
headers: { "content-type": "application/json" },
|
data/real_code2lora/code2lora-evo/.cache/huggingface/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
*
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/CACHEDIR.TAG
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
Signature: 8a477f597d28d172789f06886806bc55
|
| 2 |
-
# This file is a cache directory tag created by huggingface_hub.
|
| 3 |
-
# For information about cache directory tags, see:
|
| 4 |
-
# https://bford.info/cachedir/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/.gitattributes.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
bed0738c7eeb449bca98b5d2f33c89a1ee56349a
|
| 3 |
-
1784887206.240134
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/README.md.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
12076d4a9bd34d0eed0b624159820e7c340ac3df
|
| 3 |
-
1784887206.477736
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
1472bd42bed51428bf1bccbedc4c494db58bc9a6fffa72fc95f95a0613de2169
|
| 3 |
-
1784887250.161078
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
f73959d38ad7d479a3b6715d96ae22a42ea89c4e9b1830e3f9354ebada98877f
|
| 3 |
-
1784887281.485605
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/commits/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
6dfca1e1791fde09c1f310f2be9273b762332f28563d203a2ed7f0136cb3590b
|
| 3 |
-
1784887367.7020202
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
fc42611e215975824582df0da76984f6a65c3d5e29002a2b205fb5437cde907b
|
| 3 |
-
1784887229.2656882
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
3d76ca372ce366ab6f2b51765c9b5cbfbbfa33019b4e05d1dc05c25cacbea04f
|
| 3 |
-
1784887263.0436091
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/ir_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
af20fba7881a55b68efd82302b108615c6a5ae05be8e71d49c3455a7bbb124a4
|
| 3 |
-
1784887233.92661
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/ir_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
6b830ddb82490f1c7b6c44104cdcbcd1a8c9ca5f31d3c04af0421d2f0825c6a3
|
| 3 |
-
1784887277.8217332
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/download/qna/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
93a9e8240b9482092ecc768b804cc62bae1e875a
|
| 2 |
-
519a092970f55750571c5223b1cfbcb0d36745a678cf41b48d4de6104c2a34e7
|
| 3 |
-
1784887371.999535
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-evo/.cache/huggingface/trees/93a9e8240b9482092ecc768b804cc62bae1e875a.json
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"format_version": 1,
|
| 3 |
-
"files": {
|
| 4 |
-
".gitattributes": {
|
| 5 |
-
"size": 2504,
|
| 6 |
-
"blob_id": "bed0738c7eeb449bca98b5d2f33c89a1ee56349a"
|
| 7 |
-
},
|
| 8 |
-
"README.md": {
|
| 9 |
-
"size": 1735,
|
| 10 |
-
"blob_id": "12076d4a9bd34d0eed0b624159820e7c340ac3df"
|
| 11 |
-
},
|
| 12 |
-
"commits/cr_test.parquet": {
|
| 13 |
-
"size": 68411936,
|
| 14 |
-
"blob_id": "1e2ac27253f7cf41412b4a38520e97531302d968",
|
| 15 |
-
"lfs_sha256": "1472bd42bed51428bf1bccbedc4c494db58bc9a6fffa72fc95f95a0613de2169",
|
| 16 |
-
"lfs_size": 68411936,
|
| 17 |
-
"xet_hash": "6e8f60efddd8c7caa8df21b2abec0198eb76cf6f6a455bdca73db38493870f0d"
|
| 18 |
-
},
|
| 19 |
-
"commits/cr_val.parquet": {
|
| 20 |
-
"size": 88017946,
|
| 21 |
-
"blob_id": "75709b935d623b7edb5ccd83dc0282d1440ac580",
|
| 22 |
-
"lfs_sha256": "f73959d38ad7d479a3b6715d96ae22a42ea89c4e9b1830e3f9354ebada98877f",
|
| 23 |
-
"lfs_size": 88017946,
|
| 24 |
-
"xet_hash": "471f20effb87b24b7cb2f743e483c2e8d0799df56f1d33ff029e45552e31bbfb"
|
| 25 |
-
},
|
| 26 |
-
"commits/train.parquet": {
|
| 27 |
-
"size": 612508015,
|
| 28 |
-
"blob_id": "ff02daf9b1c12e369523125b664706b7dba70169",
|
| 29 |
-
"lfs_sha256": "6dfca1e1791fde09c1f310f2be9273b762332f28563d203a2ed7f0136cb3590b",
|
| 30 |
-
"lfs_size": 612508015,
|
| 31 |
-
"xet_hash": "3d5479420b6a2cd17c9556b3235df14df1caacc9f75eac1e530a76f9a8d1d28a"
|
| 32 |
-
},
|
| 33 |
-
"qna/cr_test.parquet": {
|
| 34 |
-
"size": 16240753,
|
| 35 |
-
"blob_id": "5d5e537f81519bb5fc611cffc1d2f3e6a643403d",
|
| 36 |
-
"lfs_sha256": "fc42611e215975824582df0da76984f6a65c3d5e29002a2b205fb5437cde907b",
|
| 37 |
-
"lfs_size": 16240753,
|
| 38 |
-
"xet_hash": "8ee16b74836d3602cf10e960cba16f083e25443e7803da5f411c9af49b3b0b12"
|
| 39 |
-
},
|
| 40 |
-
"qna/cr_val.parquet": {
|
| 41 |
-
"size": 23488346,
|
| 42 |
-
"blob_id": "b979c9c6fc248831d906057a88aa7c123eede5b4",
|
| 43 |
-
"lfs_sha256": "3d76ca372ce366ab6f2b51765c9b5cbfbbfa33019b4e05d1dc05c25cacbea04f",
|
| 44 |
-
"lfs_size": 23488346,
|
| 45 |
-
"xet_hash": "a7764c559c4dda09f842dd904c11d4c7ae340c3b3d941e15fb5e6706cacec1be"
|
| 46 |
-
},
|
| 47 |
-
"qna/ir_test.parquet": {
|
| 48 |
-
"size": 22805812,
|
| 49 |
-
"blob_id": "e997fa64278ff967d0c08c22bb9c7a5571c343d5",
|
| 50 |
-
"lfs_sha256": "af20fba7881a55b68efd82302b108615c6a5ae05be8e71d49c3455a7bbb124a4",
|
| 51 |
-
"lfs_size": 22805812,
|
| 52 |
-
"xet_hash": "9c268ba844d32ab694baf09c446c689289b9e873e4de8f9b9d3cae5470380450"
|
| 53 |
-
},
|
| 54 |
-
"qna/ir_val.parquet": {
|
| 55 |
-
"size": 20154479,
|
| 56 |
-
"blob_id": "4936c3c5f14b99c30b094b696a7df5f566745ffb",
|
| 57 |
-
"lfs_sha256": "6b830ddb82490f1c7b6c44104cdcbcd1a8c9ca5f31d3c04af0421d2f0825c6a3",
|
| 58 |
-
"lfs_size": 20154479,
|
| 59 |
-
"xet_hash": "d51d246ad2ee31868d3a7d16890466349427120f47ef7fc278181d8e1e3d879d"
|
| 60 |
-
},
|
| 61 |
-
"qna/train.parquet": {
|
| 62 |
-
"size": 135352731,
|
| 63 |
-
"blob_id": "647f3e27841055c9538eec4495520d63e30f8cb6",
|
| 64 |
-
"lfs_sha256": "519a092970f55750571c5223b1cfbcb0d36745a678cf41b48d4de6104c2a34e7",
|
| 65 |
-
"lfs_size": 135352731,
|
| 66 |
-
"xet_hash": "2a1f2087a42350f3ab6f79cda6d7be87ef81f70f3b25f0ed717463812ccdb951"
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
*
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/CACHEDIR.TAG
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
Signature: 8a477f597d28d172789f06886806bc55
|
| 2 |
-
# This file is a cache directory tag created by huggingface_hub.
|
| 3 |
-
# For information about cache directory tags, see:
|
| 4 |
-
# https://bford.info/cachedir/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/.gitattributes.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
bed0738c7eeb449bca98b5d2f33c89a1ee56349a
|
| 3 |
-
1784887199.592155
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/README.md.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
1ee8621fd5030865aab5bef7ac72820d1778810c
|
| 3 |
-
1784887199.592134
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
2f9f2dfd78618cbb7e39c73deaed6ff9508fccd2491c8aeb4f172797d26b41aa
|
| 3 |
-
1784887229.378752
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
281812bd8d81620a83fa8bac002dbbda6c71ebbfd6a47c1df5c623be41992591
|
| 3 |
-
1784887234.453962
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/ir_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
e0cffc06d2c2f5d42ad42f2ce1b6b32ec54dbde46691cc78bc1e2bd4bb5d02e2
|
| 3 |
-
1784887216.458067
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/ir_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
1efa4e7298807fe75ecd81a099908a42e3378eed80cd568659c816f96ab19fa1
|
| 3 |
-
1784887206.041879
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/commits/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
e9af541dd34eaf8ec4287b06dfc1b3a5b29f90f1ce7499a51855695786d6a3d2
|
| 3 |
-
1784887202.258442
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
fc42611e215975824582df0da76984f6a65c3d5e29002a2b205fb5437cde907b
|
| 3 |
-
1784887213.327064
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
3d76ca372ce366ab6f2b51765c9b5cbfbbfa33019b4e05d1dc05c25cacbea04f
|
| 3 |
-
1784887225.967221
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/ir_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
af20fba7881a55b68efd82302b108615c6a5ae05be8e71d49c3455a7bbb124a4
|
| 3 |
-
1784887241.1003609
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/ir_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
6b830ddb82490f1c7b6c44104cdcbcd1a8c9ca5f31d3c04af0421d2f0825c6a3
|
| 3 |
-
1784887232.856902
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/download/qna/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
65b7c77eed9398fc656052d058e9af7c2d432177
|
| 2 |
-
b2fbe5bcd7e916c8d750b654a5b0b7286578f938ed65985eca68cd2936fb5dd1
|
| 3 |
-
1784887223.748132
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static-anchor/.cache/huggingface/trees/65b7c77eed9398fc656052d058e9af7c2d432177.json
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"format_version": 1,
|
| 3 |
-
"files": {
|
| 4 |
-
".gitattributes": {
|
| 5 |
-
"size": 2504,
|
| 6 |
-
"blob_id": "bed0738c7eeb449bca98b5d2f33c89a1ee56349a"
|
| 7 |
-
},
|
| 8 |
-
"README.md": {
|
| 9 |
-
"size": 1798,
|
| 10 |
-
"blob_id": "1ee8621fd5030865aab5bef7ac72820d1778810c"
|
| 11 |
-
},
|
| 12 |
-
"commits/cr_test.parquet": {
|
| 13 |
-
"size": 20979060,
|
| 14 |
-
"blob_id": "f1a4c3af961e9f98348e4d7ca9ee7fb9ecc07bbc",
|
| 15 |
-
"lfs_sha256": "2f9f2dfd78618cbb7e39c73deaed6ff9508fccd2491c8aeb4f172797d26b41aa",
|
| 16 |
-
"lfs_size": 20979060,
|
| 17 |
-
"xet_hash": "dccd2b6b33cb58024aba877ce8638a79576dbe444b902e054c45d0cc0f873f3a"
|
| 18 |
-
},
|
| 19 |
-
"commits/cr_val.parquet": {
|
| 20 |
-
"size": 26834450,
|
| 21 |
-
"blob_id": "240e8431f81a6332c9b954f4254129e6bef363e4",
|
| 22 |
-
"lfs_sha256": "281812bd8d81620a83fa8bac002dbbda6c71ebbfd6a47c1df5c623be41992591",
|
| 23 |
-
"lfs_size": 26834450,
|
| 24 |
-
"xet_hash": "bfcfcb58af8285525bc2e48a53fe34c40e8d0bb1ba0949e9f6bc8d3f1889211e"
|
| 25 |
-
},
|
| 26 |
-
"commits/ir_test.parquet": {
|
| 27 |
-
"size": 18069075,
|
| 28 |
-
"blob_id": "869388baf5ac4d1634bf743c988829540ba0318f",
|
| 29 |
-
"lfs_sha256": "e0cffc06d2c2f5d42ad42f2ce1b6b32ec54dbde46691cc78bc1e2bd4bb5d02e2",
|
| 30 |
-
"lfs_size": 18069075,
|
| 31 |
-
"xet_hash": "182e622aa94cc1f257d5e8d43b30f54e4b2497dd66dfdd3d54884d42fc27f02a"
|
| 32 |
-
},
|
| 33 |
-
"commits/ir_val.parquet": {
|
| 34 |
-
"size": 16741758,
|
| 35 |
-
"blob_id": "4a0def30c7d746a5eead7de1a977131825ccfc49",
|
| 36 |
-
"lfs_sha256": "1efa4e7298807fe75ecd81a099908a42e3378eed80cd568659c816f96ab19fa1",
|
| 37 |
-
"lfs_size": 16741758,
|
| 38 |
-
"xet_hash": "ba00255aaa6c3f1bd3e297fd8917868a79591eb66bbc2051d79626d3628df556"
|
| 39 |
-
},
|
| 40 |
-
"commits/train.parquet": {
|
| 41 |
-
"size": 1608634,
|
| 42 |
-
"blob_id": "ef5092d91d972ceafe29e6591c2cb145255cdfa9",
|
| 43 |
-
"lfs_sha256": "e9af541dd34eaf8ec4287b06dfc1b3a5b29f90f1ce7499a51855695786d6a3d2",
|
| 44 |
-
"lfs_size": 1608634,
|
| 45 |
-
"xet_hash": "45120132a92dfd70678b173eb110756e934fc6567c1347e04d3272aa148b5c1c"
|
| 46 |
-
},
|
| 47 |
-
"qna/cr_test.parquet": {
|
| 48 |
-
"size": 16240753,
|
| 49 |
-
"blob_id": "5d5e537f81519bb5fc611cffc1d2f3e6a643403d",
|
| 50 |
-
"lfs_sha256": "fc42611e215975824582df0da76984f6a65c3d5e29002a2b205fb5437cde907b",
|
| 51 |
-
"lfs_size": 16240753,
|
| 52 |
-
"xet_hash": "8ee16b74836d3602cf10e960cba16f083e25443e7803da5f411c9af49b3b0b12"
|
| 53 |
-
},
|
| 54 |
-
"qna/cr_val.parquet": {
|
| 55 |
-
"size": 23488346,
|
| 56 |
-
"blob_id": "b979c9c6fc248831d906057a88aa7c123eede5b4",
|
| 57 |
-
"lfs_sha256": "3d76ca372ce366ab6f2b51765c9b5cbfbbfa33019b4e05d1dc05c25cacbea04f",
|
| 58 |
-
"lfs_size": 23488346,
|
| 59 |
-
"xet_hash": "a7764c559c4dda09f842dd904c11d4c7ae340c3b3d941e15fb5e6706cacec1be"
|
| 60 |
-
},
|
| 61 |
-
"qna/ir_test.parquet": {
|
| 62 |
-
"size": 22805812,
|
| 63 |
-
"blob_id": "e997fa64278ff967d0c08c22bb9c7a5571c343d5",
|
| 64 |
-
"lfs_sha256": "af20fba7881a55b68efd82302b108615c6a5ae05be8e71d49c3455a7bbb124a4",
|
| 65 |
-
"lfs_size": 22805812,
|
| 66 |
-
"xet_hash": "9c268ba844d32ab694baf09c446c689289b9e873e4de8f9b9d3cae5470380450"
|
| 67 |
-
},
|
| 68 |
-
"qna/ir_val.parquet": {
|
| 69 |
-
"size": 20154479,
|
| 70 |
-
"blob_id": "4936c3c5f14b99c30b094b696a7df5f566745ffb",
|
| 71 |
-
"lfs_sha256": "6b830ddb82490f1c7b6c44104cdcbcd1a8c9ca5f31d3c04af0421d2f0825c6a3",
|
| 72 |
-
"lfs_size": 20154479,
|
| 73 |
-
"xet_hash": "d51d246ad2ee31868d3a7d16890466349427120f47ef7fc278181d8e1e3d879d"
|
| 74 |
-
},
|
| 75 |
-
"qna/train.parquet": {
|
| 76 |
-
"size": 7436646,
|
| 77 |
-
"blob_id": "b2fcda6096ba240d7aa7bd6c1d8ab8ef1ef7eb2c",
|
| 78 |
-
"lfs_sha256": "b2fbe5bcd7e916c8d750b654a5b0b7286578f938ed65985eca68cd2936fb5dd1",
|
| 79 |
-
"lfs_size": 7436646,
|
| 80 |
-
"xet_hash": "1a44ec7585045f24c0fe8a3eb42d7068e552d0bc74fc5c94af75cb66c74a1cdf"
|
| 81 |
-
}
|
| 82 |
-
}
|
| 83 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
*
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/CACHEDIR.TAG
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
Signature: 8a477f597d28d172789f06886806bc55
|
| 2 |
-
# This file is a cache directory tag created by huggingface_hub.
|
| 3 |
-
# For information about cache directory tags, see:
|
| 4 |
-
# https://bford.info/cachedir/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/.gitattributes.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
bed0738c7eeb449bca98b5d2f33c89a1ee56349a
|
| 3 |
-
1784887242.232571
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/README.md.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
0a390fc9888809d1fa519fee1965c070112e15dd
|
| 3 |
-
1784887242.3327308
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
234545feb01789ccfd422d0a02eea7bb43e5587e6be3e90ceca24012b7e665d7
|
| 3 |
-
1784887249.4468322
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
9c05111449e596a1817f61f3b2d728f9ae11031a425bc46e565373897db49272
|
| 3 |
-
1784887248.887584
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/ir_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
7e6261be02c00201b7ed61950cebbeb1297f19f1de92ede70a9e63943747476a
|
| 3 |
-
1784887255.800129
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/ir_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
2a1ba08fee4af69d321c8278493a5c3256ea96861c7f35d785e7c90be4657ec6
|
| 3 |
-
1784887257.502165
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/qna/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
d5657224e62418a77187285fe55a650ebad1a264bb9edbdc20c7d1e132c9b523
|
| 3 |
-
1784887281.037739
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/cr_test.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
0d597c8a2462fd407394db40ab9025dc4f4ccc0697a2d4490fdd2a1eab91578f
|
| 3 |
-
1784887243.962303
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/cr_val.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
8ac1c2477884783356be16f4e2ec5c94c60823df8befb4a2d80813f40c84b794
|
| 3 |
-
1784887244.5128582
|
|
|
|
|
|
|
|
|
|
|
|
data/real_code2lora/code2lora-static/.cache/huggingface/download/repos/train.parquet.metadata
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
5ebfd4fabc36b4daf5fb2b056f48ce2d67d3a9d4
|
| 2 |
-
ad382500d5bb420e0c8064e7f79d8a26fc12aa8e5b209c95b145a0b4a18dd8ba
|
| 3 |
-
1784887245.4021702
|
|
|
|
|
|
|
|
|
|
|
|