Delete app.py
Browse files
app.py
DELETED
|
@@ -1,722 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
# -*- coding: utf-8 -*-
|
| 3 |
-
"""
|
| 4 |
-
open_image_ai_hivemind.py
|
| 5 |
-
Single-file, open-source suite:
|
| 6 |
-
- Image AI (offline-capable) with bilingual GUI (FA/EN)
|
| 7 |
-
- Code Learner Worm (self-improving patcher with tests/linters)
|
| 8 |
-
- Open-Source Harvester (local+URL indexing and modular plug-in loader)
|
| 9 |
-
No paid APIs; no accounts; uses local and open tools only.
|
| 10 |
-
|
| 11 |
-
Run:
|
| 12 |
-
python open_image_ai_hivemind.py
|
| 13 |
-
|
| 14 |
-
Build onefile (optional):
|
| 15 |
-
python -m pip install pyinstaller
|
| 16 |
-
pyinstaller --onefile open_image_ai_hivemind.py
|
| 17 |
-
"""
|
| 18 |
-
|
| 19 |
-
import os
|
| 20 |
-
import sys
|
| 21 |
-
import json
|
| 22 |
-
import time
|
| 23 |
-
import math
|
| 24 |
-
import hashlib
|
| 25 |
-
import random
|
| 26 |
-
import threading
|
| 27 |
-
import subprocess
|
| 28 |
-
import shutil
|
| 29 |
-
import re
|
| 30 |
-
from datetime import datetime
|
| 31 |
-
from pathlib import Path
|
| 32 |
-
import importlib.util
|
| 33 |
-
|
| 34 |
-
# Lazy installer (open-source libs only)
|
| 35 |
-
def _lazy_install(pkg):
|
| 36 |
-
try:
|
| 37 |
-
__import__(pkg)
|
| 38 |
-
except ImportError:
|
| 39 |
-
os.system(f"{sys.executable} -m pip install --quiet {pkg}")
|
| 40 |
-
|
| 41 |
-
for pkg in ("torch", "diffusers", "accelerate", "safetensors", "Pillow"):
|
| 42 |
-
_lazy_install(pkg)
|
| 43 |
-
|
| 44 |
-
import tkinter as tk
|
| 45 |
-
from tkinter import ttk, filedialog, messagebox
|
| 46 |
-
import torch
|
| 47 |
-
from PIL import Image, ImageOps, ImageFilter, ImageDraw
|
| 48 |
-
from diffusers import StableDiffusionPipeline, DiffusionPipeline
|
| 49 |
-
|
| 50 |
-
# ---------------------------
|
| 51 |
-
# Paths & Directories
|
| 52 |
-
# ---------------------------
|
| 53 |
-
APP_NAME = "Open Image AI Hivemind"
|
| 54 |
-
ROOT_DIR = Path(__file__).parent.resolve()
|
| 55 |
-
OUTPUT_DIR = Path(os.environ.get("OIA_OUT", ROOT_DIR / "outputs")).resolve()
|
| 56 |
-
META_DIR = OUTPUT_DIR / "meta"
|
| 57 |
-
CACHE_DIR = OUTPUT_DIR / "cache"
|
| 58 |
-
TMP_DIR = OUTPUT_DIR / "tmp"
|
| 59 |
-
PLUGINS_DIR = ROOT_DIR / "plugins"
|
| 60 |
-
HARVEST_DIR = ROOT_DIR / "harvest"
|
| 61 |
-
for d in (OUTPUT_DIR, META_DIR, CACHE_DIR, TMP_DIR, PLUGINS_DIR, HARVEST_DIR):
|
| 62 |
-
d.mkdir(parents=True, exist_ok=True)
|
| 63 |
-
|
| 64 |
-
# ---------------------------
|
| 65 |
-
# Image AI Config
|
| 66 |
-
# ---------------------------
|
| 67 |
-
DEFAULT_STEPS = int(os.environ.get("OIA_STEPS", "30"))
|
| 68 |
-
DEFAULT_GUIDANCE = float(os.environ.get("OIA_GUIDANCE", "7.5"))
|
| 69 |
-
MAX_QUEUE = int(os.environ.get("OIA_MAX_QUEUE", "64"))
|
| 70 |
-
SEED_AUTOMATIC = -1
|
| 71 |
-
|
| 72 |
-
OPEN_MODELS = {
|
| 73 |
-
"SDXL (base)": "stabilityai/stable-diffusion-xl-base-1.0",
|
| 74 |
-
"SD 1.5 (classic)": "runwayml/stable-diffusion-v1-5",
|
| 75 |
-
# Add more open models if diffusers supports them
|
| 76 |
-
# "Stable Cascade": "stabilityai/stable-cascade",
|
| 77 |
-
# "OpenJourney": "prompthero/openjourney-v4",
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
# ---------------------------
|
| 81 |
-
# Prompt utilities
|
| 82 |
-
# ---------------------------
|
| 83 |
-
OPTICAL_ILLUSION_PRESETS = [
|
| 84 |
-
"high-contrast black and white optical illusion, impossible geometry, Penrose stairs, concentric lines, parallax shifts",
|
| 85 |
-
"moiré patterns, nested grids, Escher-inspired recursion, tilt-shift depth cues, perspective ambiguity",
|
| 86 |
-
"rotational symmetry, non-Euclidean corridor, variable line thickness, alternating diagonals, dizzying parallax",
|
| 87 |
-
"spiral tunnels, zigzag pathways, impossible cube, nested frames, sharp edges, stark contrast",
|
| 88 |
-
]
|
| 89 |
-
|
| 90 |
-
WEIGHTS = [
|
| 91 |
-
"hyper-detailed", "vector-sharp edges", "ultra high resolution",
|
| 92 |
-
"minimal noise", "geometric precision", "dynamic parallax",
|
| 93 |
-
"photorealistic lighting"
|
| 94 |
-
]
|
| 95 |
-
|
| 96 |
-
MODS = [
|
| 97 |
-
"isometric view", "45-degree inclination", "multi-angle perception",
|
| 98 |
-
"viewer-dependent illusion", "top-down perspective"
|
| 99 |
-
]
|
| 100 |
-
|
| 101 |
-
def vary_prompt(base: str) -> str:
|
| 102 |
-
extra = ", ".join(random.sample(OPTICAL_ILLUSION_PRESETS, k=random.randint(1, 3)))
|
| 103 |
-
hint = ", ".join(random.sample(WEIGHTS, k=random.randint(2, 4)))
|
| 104 |
-
mod = random.choice(MODS)
|
| 105 |
-
return f"{base}, {extra}, {hint}, {mod}"
|
| 106 |
-
|
| 107 |
-
def prompt_hash(p: str) -> str:
|
| 108 |
-
return hashlib.sha256(p.encode("utf-8")).hexdigest()[:16]
|
| 109 |
-
|
| 110 |
-
# ---------------------------
|
| 111 |
-
# Cache & Meta
|
| 112 |
-
# ---------------------------
|
| 113 |
-
def cache_path_for(prompt: str) -> Path:
|
| 114 |
-
return CACHE_DIR / f"{prompt_hash(prompt)}.png"
|
| 115 |
-
|
| 116 |
-
def cached(prompt: str) -> Path | None:
|
| 117 |
-
p = cache_path_for(prompt)
|
| 118 |
-
return p if p.exists() else None
|
| 119 |
-
|
| 120 |
-
def write_meta(meta: dict, fname: Path):
|
| 121 |
-
meta["timestamp"] = datetime.utcnow().isoformat()
|
| 122 |
-
with open(META_DIR / (fname.stem + ".json"), "w", encoding="utf-8") as f:
|
| 123 |
-
json.dump(meta, f, ensure_ascii=False, indent=2)
|
| 124 |
-
|
| 125 |
-
# ---------------------------
|
| 126 |
-
# Anti-error (Image AI)
|
| 127 |
-
# ---------------------------
|
| 128 |
-
class AntiError:
|
| 129 |
-
def __init__(self):
|
| 130 |
-
self.fail_count = 0
|
| 131 |
-
self.circuit_open = False
|
| 132 |
-
self.warned_no_model = False
|
| 133 |
-
|
| 134 |
-
def reset(self):
|
| 135 |
-
self.fail_count = 0
|
| 136 |
-
self.circuit_open = False
|
| 137 |
-
|
| 138 |
-
def bump(self):
|
| 139 |
-
self.fail_count += 1
|
| 140 |
-
if self.fail_count >= 5:
|
| 141 |
-
self.circuit_open = True
|
| 142 |
-
|
| 143 |
-
def warn_model_missing(self):
|
| 144 |
-
if not self.warned_no_model:
|
| 145 |
-
print("[!] No model loaded. Using local illusion fallback.")
|
| 146 |
-
self.warned_no_model = True
|
| 147 |
-
|
| 148 |
-
anti = AntiError()
|
| 149 |
-
|
| 150 |
-
# ---------------------------
|
| 151 |
-
# Local illusion generator
|
| 152 |
-
# ---------------------------
|
| 153 |
-
def local_illusion(size=1024) -> Image.Image:
|
| 154 |
-
img = Image.new("RGB", (size, size), "white")
|
| 155 |
-
draw = ImageDraw.Draw(img)
|
| 156 |
-
step = 6
|
| 157 |
-
for r in range(24, size//2, step):
|
| 158 |
-
val = 0 if (r//step) % 2 == 0 else 255
|
| 159 |
-
for x in range(size):
|
| 160 |
-
if abs(x - size//2) == r:
|
| 161 |
-
draw.line([(x, 0), (x, size)], fill=(val, val, val))
|
| 162 |
-
for y in range(size):
|
| 163 |
-
if abs(y - size//2) == r:
|
| 164 |
-
draw.line([(0, y), (size, y)], fill=(val, val, val))
|
| 165 |
-
hatch = Image.new("L", (size, size), color=255)
|
| 166 |
-
hpx = hatch.load()
|
| 167 |
-
for x in range(size):
|
| 168 |
-
for y in range(size):
|
| 169 |
-
if (x + y) % 18 == 0:
|
| 170 |
-
hpx[x, y] = 0
|
| 171 |
-
merged = Image.merge("RGB", (ImageOps.autocontrast(hatch), ImageOps.autocontrast(hatch), ImageOps.autocontrast(hatch)))
|
| 172 |
-
merged = Image.blend(img, merged, 0.35).filter(ImageFilter.SHARPEN)
|
| 173 |
-
merged = merged.rotate(random.choice([4, -6, 8, -10]), resample=Image.BICUBIC, expand=False)
|
| 174 |
-
return merged
|
| 175 |
-
|
| 176 |
-
# ---------------------------
|
| 177 |
-
# Post-process filters
|
| 178 |
-
# ---------------------------
|
| 179 |
-
POST_MODES = [
|
| 180 |
-
"illusion_boost", "bw_high", "moire_mix", "invert", "edge_halo",
|
| 181 |
-
"soft_glow", "posterize4", "contrast_max", "rotate_slight", "grain_fine"
|
| 182 |
-
]
|
| 183 |
-
|
| 184 |
-
def post_process(img: Image.Image, mode: str) -> Image.Image:
|
| 185 |
-
if mode == "illusion_boost":
|
| 186 |
-
out = ImageOps.autocontrast(img).filter(ImageFilter.UnsharpMask(radius=2, percent=180, threshold=3))
|
| 187 |
-
elif mode == "bw_high":
|
| 188 |
-
out = ImageOps.autocontrast(ImageOps.grayscale(img)).convert("RGB")
|
| 189 |
-
elif mode == "moire_mix":
|
| 190 |
-
out = ImageOps.posterize(img.filter(ImageFilter.DETAIL).filter(ImageFilter.SHARPEN), bits=4)
|
| 191 |
-
elif mode == "invert":
|
| 192 |
-
out = ImageOps.invert(img)
|
| 193 |
-
elif mode == "edge_halo":
|
| 194 |
-
out = img.filter(ImageFilter.UnsharpMask(radius=3, percent=240, threshold=2))
|
| 195 |
-
elif mode == "soft_glow":
|
| 196 |
-
blur = img.filter(ImageFilter.GaussianBlur(radius=2))
|
| 197 |
-
out = Image.blend(img, blur, alpha=0.3)
|
| 198 |
-
elif mode == "posterize4":
|
| 199 |
-
out = ImageOps.posterize(img, bits=4)
|
| 200 |
-
elif mode == "contrast_max":
|
| 201 |
-
out = ImageOps.autocontrast(img)
|
| 202 |
-
elif mode == "rotate_slight":
|
| 203 |
-
out = img.rotate(random.choice([-3, 3, -2, 2]), resample=Image.BICUBIC, expand=False)
|
| 204 |
-
elif mode == "grain_fine":
|
| 205 |
-
out = img.filter(ImageFilter.SMOOTH_MORE)
|
| 206 |
-
else:
|
| 207 |
-
out = img
|
| 208 |
-
return out
|
| 209 |
-
|
| 210 |
-
# ---------------------------
|
| 211 |
-
# Model Hub
|
| 212 |
-
# ---------------------------
|
| 213 |
-
class ModelHub:
|
| 214 |
-
def __init__(self):
|
| 215 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 216 |
-
self.pipe = None
|
| 217 |
-
self.model_id = None
|
| 218 |
-
|
| 219 |
-
def load(self, model_id: str, fp16=True):
|
| 220 |
-
self.model_id = model_id
|
| 221 |
-
dtype = torch.float16 if (fp16 and self.device == "cuda") else torch.float32
|
| 222 |
-
try:
|
| 223 |
-
if "stable-diffusion-xl" in model_id:
|
| 224 |
-
self.pipe = DiffusionPipeline.from_pretrained(model_id, torch_dtype=dtype, variant="fp16" if dtype==torch.float16 else None)
|
| 225 |
-
else:
|
| 226 |
-
self.pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=dtype)
|
| 227 |
-
self.pipe = self.pipe.to(self.device)
|
| 228 |
-
if self.device == "cuda":
|
| 229 |
-
self.pipe.enable_attention_slicing()
|
| 230 |
-
anti.reset()
|
| 231 |
-
return True
|
| 232 |
-
except Exception as e:
|
| 233 |
-
print(f"[!] Model load error: {e}")
|
| 234 |
-
self.pipe = None
|
| 235 |
-
return False
|
| 236 |
-
|
| 237 |
-
def generate(self, prompt: str, steps: int, guidance: float, seed: int | None, height=768, width=768) -> Image.Image:
|
| 238 |
-
if self.pipe is None:
|
| 239 |
-
anti.warn_model_missing()
|
| 240 |
-
return local_illusion(size=min(height, width))
|
| 241 |
-
generator = torch.Generator(device=self.device)
|
| 242 |
-
if seed is not None and seed >= 0:
|
| 243 |
-
generator = generator.manual_seed(seed)
|
| 244 |
-
try:
|
| 245 |
-
out = self.pipe(prompt, height=height, width=width, num_inference_steps=steps, guidance_scale=guidance, generator=generator)
|
| 246 |
-
return out.images[0]
|
| 247 |
-
except Exception as e:
|
| 248 |
-
print(f"[!] Generate error: {e}")
|
| 249 |
-
anti.bump()
|
| 250 |
-
return local_illusion(size=min(height, width))
|
| 251 |
-
|
| 252 |
-
hub = ModelHub()
|
| 253 |
-
|
| 254 |
-
# ---------------------------
|
| 255 |
-
# Orchestrator (Image Queue)
|
| 256 |
-
# ---------------------------
|
| 257 |
-
class Orchestrator:
|
| 258 |
-
def __init__(self):
|
| 259 |
-
self.queue = []
|
| 260 |
-
self.running = False
|
| 261 |
-
|
| 262 |
-
def enqueue(self, prompt, count, vary, steps, guidance, seed, size, post_mode):
|
| 263 |
-
for _ in range(count):
|
| 264 |
-
if len(self.queue) >= MAX_QUEUE:
|
| 265 |
-
break
|
| 266 |
-
self.queue.append({
|
| 267 |
-
"prompt": prompt,
|
| 268 |
-
"vary": vary,
|
| 269 |
-
"steps": steps,
|
| 270 |
-
"guidance": guidance,
|
| 271 |
-
"seed": seed,
|
| 272 |
-
"size": size,
|
| 273 |
-
"post_mode": post_mode
|
| 274 |
-
})
|
| 275 |
-
|
| 276 |
-
def _do_item(self, item):
|
| 277 |
-
p = item["prompt"]
|
| 278 |
-
if item["vary"]:
|
| 279 |
-
p = vary_prompt(p)
|
| 280 |
-
cp = cached(p)
|
| 281 |
-
if cp:
|
| 282 |
-
img = Image.open(cp).convert("RGB")
|
| 283 |
-
else:
|
| 284 |
-
img = hub.generate(p, item["steps"], item["guidance"], item["seed"], item["size"], item["size"])
|
| 285 |
-
try:
|
| 286 |
-
cache_path = cache_path_for(p)
|
| 287 |
-
img.save(cache_path, "PNG", optimize=True)
|
| 288 |
-
except Exception as e:
|
| 289 |
-
print(f"[!] Cache save error: {e}")
|
| 290 |
-
if item["post_mode"]:
|
| 291 |
-
img = post_process(img, item["post_mode"])
|
| 292 |
-
fname = OUTPUT_DIR / f"{datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')}_{prompt_hash(p)}.png"
|
| 293 |
-
img.save(fname, "PNG", optimize=True)
|
| 294 |
-
write_meta({"prompt": p, "model": hub.model_id or "local_illusion", "post_mode": item["post_mode"]}, fname)
|
| 295 |
-
return fname
|
| 296 |
-
|
| 297 |
-
def run(self, on_log):
|
| 298 |
-
if self.running:
|
| 299 |
-
return
|
| 300 |
-
self.running = True
|
| 301 |
-
try:
|
| 302 |
-
while self.queue:
|
| 303 |
-
item = self.queue.pop(0)
|
| 304 |
-
try:
|
| 305 |
-
path = self._do_item(item)
|
| 306 |
-
on_log(f" -> Saved: {path}")
|
| 307 |
-
except Exception as e:
|
| 308 |
-
on_log(f" x Failed: {e}")
|
| 309 |
-
finally:
|
| 310 |
-
self.running = False
|
| 311 |
-
|
| 312 |
-
orch = Orchestrator()
|
| 313 |
-
|
| 314 |
-
# ---------------------------
|
| 315 |
-
# Code Learner Worm
|
| 316 |
-
# ---------------------------
|
| 317 |
-
class CodeLearner:
|
| 318 |
-
"""
|
| 319 |
-
A self-improving patcher:
|
| 320 |
-
- Scans this file and plugins for improvement opportunities.
|
| 321 |
-
- Runs tests/health checks (syntax, import, basic run).
|
| 322 |
-
- Generates small patches (safe transformations) and applies if all checks pass.
|
| 323 |
-
- Keeps history in harvest/patches.json.
|
| 324 |
-
"""
|
| 325 |
-
def __init__(self, root: Path):
|
| 326 |
-
self.root = root
|
| 327 |
-
self.target_file = root / Path(__file__).name
|
| 328 |
-
self.patches_file = HARVEST_DIR / "patches.json"
|
| 329 |
-
if not self.patches_file.exists():
|
| 330 |
-
self._write_json(self.patches_file, [])
|
| 331 |
-
|
| 332 |
-
def _read(self, p: Path) -> str:
|
| 333 |
-
return p.read_text(encoding="utf-8")
|
| 334 |
-
|
| 335 |
-
def _write(self, p: Path, s: str):
|
| 336 |
-
p.write_text(s, encoding="utf-8")
|
| 337 |
-
|
| 338 |
-
def _write_json(self, p: Path, obj):
|
| 339 |
-
with open(p, "w", encoding="utf-8") as f:
|
| 340 |
-
json.dump(obj, f, ensure_ascii=False, indent=2)
|
| 341 |
-
|
| 342 |
-
def _record_patch(self, summary, diff_preview):
|
| 343 |
-
data = json.loads(self.patches_file.read_text(encoding="utf-8"))
|
| 344 |
-
data.append({
|
| 345 |
-
"time": datetime.utcnow().isoformat(),
|
| 346 |
-
"summary": summary,
|
| 347 |
-
"preview": diff_preview[:1000]
|
| 348 |
-
})
|
| 349 |
-
self._write_json(self.patches_file, data)
|
| 350 |
-
|
| 351 |
-
def _basic_health(self) -> bool:
|
| 352 |
-
# Syntax check by importing this file as a module
|
| 353 |
-
try:
|
| 354 |
-
spec = importlib.util.spec_from_file_location("oiah", self.target_file)
|
| 355 |
-
module = importlib.util.module_from_spec(spec)
|
| 356 |
-
spec.loader.exec_module(module) # type: ignore
|
| 357 |
-
return True
|
| 358 |
-
except Exception as e:
|
| 359 |
-
print(f"[WORM] Health check failed: {e}")
|
| 360 |
-
return False
|
| 361 |
-
|
| 362 |
-
def _safe_transforms(self, code: str) -> list[tuple[str, str]]:
|
| 363 |
-
transforms = []
|
| 364 |
-
# Example 1: ensure UI theme fallback exists (idempotent)
|
| 365 |
-
if "style.theme_use(\"clam\")" not in code:
|
| 366 |
-
transforms.append(("Add clam theme fallback",
|
| 367 |
-
"style = ttk.Style()\n try:\n style.theme_use(\"clam\")\n except Exception:\n pass\n"))
|
| 368 |
-
# Example 2: add plugin loader hook if missing
|
| 369 |
-
if "load_plugins()" not in code:
|
| 370 |
-
transforms.append(("Add plugin loader hook", "load_plugins()\n"))
|
| 371 |
-
# Example 3: add Open Models expansion comment marker
|
| 372 |
-
if "# Add more open models" not in code:
|
| 373 |
-
transforms.append(("Add models marker", "# Add more open models if diffusers supports them\n"))
|
| 374 |
-
return transforms
|
| 375 |
-
|
| 376 |
-
def _apply_transform(self, original: str, transform_snippet: str) -> str:
|
| 377 |
-
# Simple heuristic: insert before main()
|
| 378 |
-
insertion_point = original.rfind("def main():")
|
| 379 |
-
if insertion_point == -1:
|
| 380 |
-
return original + "\n" + transform_snippet
|
| 381 |
-
head = original[:insertion_point]
|
| 382 |
-
tail = original[insertion_point:]
|
| 383 |
-
return head + "\n" + transform_snippet + "\n" + tail
|
| 384 |
-
|
| 385 |
-
def improve(self, on_log):
|
| 386 |
-
code = self._read(self.target_file)
|
| 387 |
-
transforms = self._safe_transforms(code)
|
| 388 |
-
if not transforms:
|
| 389 |
-
on_log("[WORM] Nothing to improve (idempotent).")
|
| 390 |
-
return
|
| 391 |
-
for summary, snippet in transforms:
|
| 392 |
-
on_log(f"[WORM] Proposing patch: {summary}")
|
| 393 |
-
patched = self._apply_transform(code, snippet)
|
| 394 |
-
backup = self.target_file.with_suffix(".bak")
|
| 395 |
-
shutil.copyfile(self.target_file, backup)
|
| 396 |
-
self._write(self.target_file, patched)
|
| 397 |
-
if self._basic_health():
|
| 398 |
-
diff_preview = self._diff_preview(code, patched)
|
| 399 |
-
self._record_patch(summary, diff_preview)
|
| 400 |
-
on_log(f"[WORM] Patch applied: {summary}")
|
| 401 |
-
code = patched # continue from patched version
|
| 402 |
-
else:
|
| 403 |
-
on_log(f"[WORM] Patch reverted: {summary}")
|
| 404 |
-
shutil.copyfile(backup, self.target_file)
|
| 405 |
-
backup.unlink(missing_ok=True)
|
| 406 |
-
|
| 407 |
-
def _diff_preview(self, old: str, new: str) -> str:
|
| 408 |
-
import difflib
|
| 409 |
-
return "\n".join(difflib.unified_diff(old.splitlines(), new.splitlines(), lineterm=""))
|
| 410 |
-
|
| 411 |
-
# ---------------------------
|
| 412 |
-
# Open-Source Harvester
|
| 413 |
-
# ---------------------------
|
| 414 |
-
class Harvester:
|
| 415 |
-
"""
|
| 416 |
-
Finds and connects open resources:
|
| 417 |
-
- Index local folders and user-provided URLs (git/http) without any paid APIs.
|
| 418 |
-
- Clones git repos and downloads raw files into harvest/.
|
| 419 |
-
- Loads plugin modules from plugins/ to extend functionality.
|
| 420 |
-
"""
|
| 421 |
-
def __init__(self, harvest_dir: Path, plugins_dir: Path):
|
| 422 |
-
self.harvest_dir = harvest_dir
|
| 423 |
-
self.plugins_dir = plugins_dir
|
| 424 |
-
|
| 425 |
-
def index_local(self, paths: list[Path]) -> list[Path]:
|
| 426 |
-
indexed = []
|
| 427 |
-
for p in paths:
|
| 428 |
-
if p.exists():
|
| 429 |
-
for root, dirs, files in os.walk(p):
|
| 430 |
-
for f in files:
|
| 431 |
-
indexed.append(Path(root) / f)
|
| 432 |
-
(self.harvest_dir / "index.json").write_text(json.dumps([str(x) for x in indexed], indent=2), encoding="utf-8")
|
| 433 |
-
return indexed
|
| 434 |
-
|
| 435 |
-
def clone_git(self, url: str, name_hint: str | None = None) -> Path | None:
|
| 436 |
-
# Requires git installed; no accounts; open repos only
|
| 437 |
-
out = self.harvest_dir / (name_hint or f"repo_{prompt_hash(url)}")
|
| 438 |
-
try:
|
| 439 |
-
subprocess.check_call(["git", "clone", "--depth", "1", url, str(out)])
|
| 440 |
-
return out
|
| 441 |
-
except Exception as e:
|
| 442 |
-
print(f"[HARVEST] Git clone failed: {e}")
|
| 443 |
-
return None
|
| 444 |
-
|
| 445 |
-
def http_fetch(self, url: str, name: str | None = None) -> Path | None:
|
| 446 |
-
# Simple open HTTP fetch (no paid API); may need allowlist
|
| 447 |
-
import urllib.request
|
| 448 |
-
try:
|
| 449 |
-
data = urllib.request.urlopen(url, timeout=30).read()
|
| 450 |
-
target = self.harvest_dir / (name or f"file_{prompt_hash(url)}")
|
| 451 |
-
target.write_bytes(data)
|
| 452 |
-
return target
|
| 453 |
-
except Exception as e:
|
| 454 |
-
print(f"[HARVEST] HTTP fetch failed: {e}")
|
| 455 |
-
return None
|
| 456 |
-
|
| 457 |
-
def load_plugins(self) -> list[str]:
|
| 458 |
-
loaded = []
|
| 459 |
-
for py in self.plugins_dir.glob("*.py"):
|
| 460 |
-
try:
|
| 461 |
-
spec = importlib.util.spec_from_file_location(py.stem, py)
|
| 462 |
-
module = importlib.util.module_from_spec(spec)
|
| 463 |
-
spec.loader.exec_module(module) # type: ignore
|
| 464 |
-
loaded.append(py.stem)
|
| 465 |
-
except Exception as e:
|
| 466 |
-
print(f"[PLUGINS] Failed to load {py}: {e}")
|
| 467 |
-
(self.harvest_dir / "plugins_loaded.json").write_text(json.dumps(loaded, indent=2), encoding="utf-8")
|
| 468 |
-
return loaded
|
| 469 |
-
|
| 470 |
-
harvester = Harvester(HARVEST_DIR, PLUGINS_DIR)
|
| 471 |
-
|
| 472 |
-
def load_plugins():
|
| 473 |
-
harvester.load_plugins()
|
| 474 |
-
|
| 475 |
-
# ---------------------------
|
| 476 |
-
# GUI (Bilingual)
|
| 477 |
-
# ---------------------------
|
| 478 |
-
class App:
|
| 479 |
-
def __init__(self, root):
|
| 480 |
-
self.root = root
|
| 481 |
-
root.title(APP_NAME)
|
| 482 |
-
root.geometry("1080x780")
|
| 483 |
-
|
| 484 |
-
self.lang = tk.StringVar(value="FA")
|
| 485 |
-
self.prompt = tk.StringVar()
|
| 486 |
-
self.count = tk.IntVar(value=3)
|
| 487 |
-
self.vary = tk.BooleanVar(value=True)
|
| 488 |
-
self.steps = tk.IntVar(value=DEFAULT_STEPS)
|
| 489 |
-
self.guidance = tk.DoubleVar(value=DEFAULT_GUIDANCE)
|
| 490 |
-
self.seed = tk.IntVar(value=SEED_AUTOMATIC)
|
| 491 |
-
self.size = tk.IntVar(value=768)
|
| 492 |
-
self.post_mode = tk.StringVar(value="")
|
| 493 |
-
self.model_choice = tk.StringVar(value=list(OPEN_MODELS.keys())[0])
|
| 494 |
-
|
| 495 |
-
# Harvester inputs
|
| 496 |
-
self.harvest_url = tk.StringVar()
|
| 497 |
-
self.harvest_local = tk.StringVar()
|
| 498 |
-
|
| 499 |
-
self._build_ui()
|
| 500 |
-
self._set_texts()
|
| 501 |
-
self._log_header()
|
| 502 |
-
|
| 503 |
-
def _build_ui(self):
|
| 504 |
-
top = ttk.Frame(self.root)
|
| 505 |
-
top.pack(fill="x", padx=10, pady=10)
|
| 506 |
-
ttk.Label(top, text="Language / زبان").pack(side="left")
|
| 507 |
-
ttk.Combobox(top, textvariable=self.lang, values=["FA", "EN"], width=5, state="readonly").pack(side="left", padx=8)
|
| 508 |
-
ttk.Button(top, text="Apply/اعمال", command=self._set_texts).pack(side="left", padx=8)
|
| 509 |
-
|
| 510 |
-
model_frame = ttk.LabelFrame(self.root, text="Model / مدل")
|
| 511 |
-
model_frame.pack(fill="x", padx=10, pady=6)
|
| 512 |
-
ttk.Label(model_frame, text="Select model:").grid(row=0, column=0, sticky="w", padx=6, pady=6)
|
| 513 |
-
ttk.Combobox(model_frame, textvariable=self.model_choice, values=list(OPEN_MODELS.keys()), width=28, state="readonly").grid(row=0, column=1, sticky="w", padx=6, pady=6)
|
| 514 |
-
ttk.Button(model_frame, text="Load model", command=self.load_model).grid(row=0, column=2, padx=6, pady=6)
|
| 515 |
-
|
| 516 |
-
mid = ttk.LabelFrame(self.root, text="Controls / کنترلها")
|
| 517 |
-
mid.pack(fill="x", padx=10, pady=6)
|
| 518 |
-
self.lbl_prompt = ttk.Label(mid, text="")
|
| 519 |
-
self.lbl_prompt.grid(row=0, column=0, sticky="w", padx=6, pady=6)
|
| 520 |
-
ttk.Entry(mid, textvariable=self.prompt, width=72).grid(row=0, column=1, sticky="we", padx=6, pady=6)
|
| 521 |
-
|
| 522 |
-
self.lbl_count = ttk.Label(mid, text="")
|
| 523 |
-
self.lbl_count.grid(row=1, column=0, sticky="w", padx=6, pady=6)
|
| 524 |
-
ttk.Spinbox(mid, from_=1, to=50, textvariable=self.count, width=6).grid(row=1, column=1, sticky="w", padx=6, pady=6)
|
| 525 |
-
|
| 526 |
-
self.lbl_vary = ttk.Label(mid, text="")
|
| 527 |
-
self.lbl_vary.grid(row=2, column=0, sticky="w", padx=6, pady=6)
|
| 528 |
-
ttk.Checkbutton(mid, text="", variable=self.vary).grid(row=2, column=1, sticky="w", padx=6, pady=6)
|
| 529 |
-
|
| 530 |
-
self.lbl_steps = ttk.Label(mid, text="")
|
| 531 |
-
self.lbl_steps.grid(row=3, column=0, sticky="w", padx=6, pady=6)
|
| 532 |
-
ttk.Spinbox(mid, from_=5, to=100, textvariable=self.steps, width=6).grid(row=3, column=1, sticky="w", padx=6, pady=6)
|
| 533 |
-
|
| 534 |
-
self.lbl_guidance = ttk.Label(mid, text="")
|
| 535 |
-
self.lbl_guidance.grid(row=4, column=0, sticky="w", padx=6, pady=6)
|
| 536 |
-
ttk.Spinbox(mid, from_=0.0, to=20.0, increment=0.5, textvariable=self.guidance, width=6).grid(row=4, column=1, sticky="w", padx=6, pady=6)
|
| 537 |
-
|
| 538 |
-
self.lbl_seed = ttk.Label(mid, text="")
|
| 539 |
-
self.lbl_seed.grid(row=5, column=0, sticky="w", padx=6, pady=6)
|
| 540 |
-
ttk.Spinbox(mid, from_=-1, to=2**31-1, textvariable=self.seed, width=12).grid(row=5, column=1, sticky="w", padx=6, pady=6)
|
| 541 |
-
|
| 542 |
-
self.lbl_size = ttk.Label(mid, text="")
|
| 543 |
-
self.lbl_size.grid(row=6, column=0, sticky="w", padx=6, pady=6)
|
| 544 |
-
ttk.Spinbox(mid, from_=256, to=1024, increment=64, textvariable=self.size, width=6).grid(row=6, column=1, sticky="w", padx=6, pady=6)
|
| 545 |
-
|
| 546 |
-
self.lbl_pp = ttk.Label(mid, text="")
|
| 547 |
-
self.lbl_pp.grid(row=7, column=0, sticky="w", padx=6, pady=6)
|
| 548 |
-
ttk.Combobox(mid, textvariable=self.post_mode, values=[""] + POST_MODES, width=20, state="readonly").grid(row=7, column=1, sticky="w", padx=6, pady=6)
|
| 549 |
-
|
| 550 |
-
btns = ttk.Frame(mid)
|
| 551 |
-
btns.grid(row=8, column=0, columnspan=2, sticky="we", padx=6, pady=6)
|
| 552 |
-
self.btn_run = ttk.Button(btns, text="", command=self.run_queue)
|
| 553 |
-
self.btn_run.pack(side="left", padx=6)
|
| 554 |
-
ttk.Button(btns, text="Open outputs", command=self.open_outputs).pack(side="left", padx=6)
|
| 555 |
-
ttk.Button(btns, text="Illusion offline test", command=self.make_offline_illusion).pack(side="left", padx=6)
|
| 556 |
-
|
| 557 |
-
# Harvester panel
|
| 558 |
-
hframe = ttk.LabelFrame(self.root, text="Open-Source Harvester")
|
| 559 |
-
hframe.pack(fill="x", padx=10, pady=10)
|
| 560 |
-
ttk.Label(hframe, text="Git/HTTP URL:").grid(row=0, column=0, sticky="w", padx=6, pady=6)
|
| 561 |
-
ttk.Entry(hframe, textvariable=self.harvest_url, width=60).grid(row=0, column=1, sticky="we", padx=6, pady=6)
|
| 562 |
-
ttk.Button(hframe, text="Clone/Fetch", command=self.harvest_fetch).grid(row=0, column=2, padx=6, pady=6)
|
| 563 |
-
|
| 564 |
-
ttk.Label(hframe, text="Local path to index:").grid(row=1, column=0, sticky="w", padx=6, pady=6)
|
| 565 |
-
ttk.Entry(hframe, textvariable=self.harvest_local, width=60).grid(row=1, column=1, sticky="we", padx=6, pady=6)
|
| 566 |
-
ttk.Button(hframe, text="Index", command=self.harvest_index).grid(row=1, column=2, padx=6, pady=6)
|
| 567 |
-
|
| 568 |
-
# Learner panel
|
| 569 |
-
lframe = ttk.LabelFrame(self.root, text="Code Learner Worm")
|
| 570 |
-
lframe.pack(fill="x", padx=10, pady=10)
|
| 571 |
-
ttk.Button(lframe, text="Analyze & Improve", command=self.run_worm).grid(row=0, column=0, padx=6, pady=6)
|
| 572 |
-
ttk.Button(lframe, text="Load plugins", command=load_plugins).grid(row=0, column=1, padx=6, pady=6)
|
| 573 |
-
|
| 574 |
-
self.log = tk.Text(self.root, height=18, wrap="word")
|
| 575 |
-
self.log.pack(fill="both", expand=True, padx=10, pady=10)
|
| 576 |
-
|
| 577 |
-
status_bar = ttk.Frame(self.root)
|
| 578 |
-
status_bar.pack(fill="x", padx=10, pady=5)
|
| 579 |
-
self.status = tk.StringVar(value="Ready")
|
| 580 |
-
self.lbl_status = ttk.Label(status_bar, textvariable=self.status)
|
| 581 |
-
self.lbl_status.pack(side="left")
|
| 582 |
-
|
| 583 |
-
def _set_texts(self):
|
| 584 |
-
if self.lang.get() == "FA":
|
| 585 |
-
self.lbl_prompt.config(text="متن راهنمای تصویر (پرومپت):")
|
| 586 |
-
self.lbl_count.config(text="تعداد تولید:")
|
| 587 |
-
self.lbl_vary.config(text="تنوعدهی خودکار پرومپت:")
|
| 588 |
-
self.lbl_steps.config(text="تعداد گامها:")
|
| 589 |
-
self.lbl_guidance.config(text="راهنمایی (Guidance):")
|
| 590 |
-
self.lbl_seed.config(text="Seed (برای تکرارپذیری، -1 خودکار):")
|
| 591 |
-
self.lbl_size.config(text="اندازه (پیکسل مربع):")
|
| 592 |
-
self.lbl_pp.config(text="پسپردازش:")
|
| 593 |
-
self.btn_run.config(text="شروع تولید")
|
| 594 |
-
self.root.title(f"{APP_NAME} - رابط فارسی")
|
| 595 |
-
else:
|
| 596 |
-
self.lbl_prompt.config(text="Image prompt:")
|
| 597 |
-
self.lbl_count.config(text="Count:")
|
| 598 |
-
self.lbl_vary.config(text="Auto prompt variation:")
|
| 599 |
-
self.lbl_steps.config(text="Steps:")
|
| 600 |
-
self.lbl_guidance.config(text="Guidance:")
|
| 601 |
-
self.lbl_seed.config(text="Seed (-1 automatic):")
|
| 602 |
-
self.lbl_size.config(text="Size (square px):")
|
| 603 |
-
self.lbl_pp.config(text="Post-process:")
|
| 604 |
-
self.btn_run.config(text="Start")
|
| 605 |
-
self.root.title(f"{APP_NAME} - English UI")
|
| 606 |
-
|
| 607 |
-
def _log_header(self):
|
| 608 |
-
self.append_log(f"[{APP_NAME}] Ready. Select a model, enter prompt, and start.")
|
| 609 |
-
|
| 610 |
-
def append_log(self, s):
|
| 611 |
-
self.log.insert("end", s + "\n")
|
| 612 |
-
self.log.see("end")
|
| 613 |
-
|
| 614 |
-
def open_outputs(self):
|
| 615 |
-
path = str(OUTPUT_DIR)
|
| 616 |
-
try:
|
| 617 |
-
if os.name == "nt":
|
| 618 |
-
os.startfile(path)
|
| 619 |
-
elif sys.platform == "darwin":
|
| 620 |
-
os.system(f'open "{path}"')
|
| 621 |
-
else:
|
| 622 |
-
os.system(f'xdg-open "{path}"')
|
| 623 |
-
except Exception:
|
| 624 |
-
messagebox.showinfo("Info", f"Outputs at: {path}")
|
| 625 |
-
|
| 626 |
-
def load_model(self):
|
| 627 |
-
name = self.model_choice.get()
|
| 628 |
-
model_id = OPEN_MODELS.get(name)
|
| 629 |
-
if not model_id:
|
| 630 |
-
messagebox.showerror("Error", "Model id not found.")
|
| 631 |
-
return
|
| 632 |
-
self.status.set("Loading model...")
|
| 633 |
-
self.append_log(f"[+] Loading: {name} -> {model_id}")
|
| 634 |
-
self.root.update_idletasks()
|
| 635 |
-
ok = hub.load(model_id, fp16=True)
|
| 636 |
-
if ok:
|
| 637 |
-
self.status.set("Model loaded")
|
| 638 |
-
self.append_log(" -> Model ready.")
|
| 639 |
-
else:
|
| 640 |
-
self.status.set("Model load failed (fallback active)")
|
| 641 |
-
self.append_log(" x Model load failed; using local illusion fallback.")
|
| 642 |
-
|
| 643 |
-
def run_queue(self):
|
| 644 |
-
base_prompt = self.prompt.get().strip()
|
| 645 |
-
if not base_prompt:
|
| 646 |
-
messagebox.showwarning("Warn", "Please enter a prompt / لطفاً پرومپت را وارد کنید")
|
| 647 |
-
return
|
| 648 |
-
count = self.count.get()
|
| 649 |
-
vary = self.vary.get()
|
| 650 |
-
steps = self.steps.get()
|
| 651 |
-
guidance = self.guidance.get()
|
| 652 |
-
seed = self.seed.get()
|
| 653 |
-
size = self.size.get()
|
| 654 |
-
pp = self.post_mode.get()
|
| 655 |
-
|
| 656 |
-
orch.enqueue(base_prompt, count, vary, steps, guidance, seed, size, pp)
|
| 657 |
-
self.status.set("Running...")
|
| 658 |
-
self.append_log(f"[+] {datetime.now().strftime('%H:%M:%S')} Enqueued: {base_prompt} x {count} vary={vary} steps={steps} guidance={guidance} seed={seed} size={size} pp={pp}")
|
| 659 |
-
|
| 660 |
-
def worker():
|
| 661 |
-
try:
|
| 662 |
-
orch.run(self.append_log)
|
| 663 |
-
finally:
|
| 664 |
-
self.status.set("Done")
|
| 665 |
-
threading.Thread(target=worker, daemon=True).start()
|
| 666 |
-
|
| 667 |
-
def make_offline_illusion(self):
|
| 668 |
-
img = local_illusion(size=self.size.get())
|
| 669 |
-
fname = OUTPUT_DIR / f"{datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')}_offline_illusion.png"
|
| 670 |
-
img.save(fname, "PNG", optimize=True)
|
| 671 |
-
write_meta({"prompt": "offline_illusion", "model": "local_illusion"}, fname)
|
| 672 |
-
self.append_log(f" -> Saved offline illusion: {fname}")
|
| 673 |
-
|
| 674 |
-
def harvest_fetch(self):
|
| 675 |
-
url = self.harvest_url.get().strip()
|
| 676 |
-
if not url:
|
| 677 |
-
messagebox.showwarning("Warn", "Enter Git/HTTP URL / لطفاً URL را وارد کنید")
|
| 678 |
-
return
|
| 679 |
-
self.append_log(f"[HARVEST] Fetching: {url}")
|
| 680 |
-
if url.endswith(".git"):
|
| 681 |
-
path = harvester.clone_git(url)
|
| 682 |
-
else:
|
| 683 |
-
name = Path(url).name
|
| 684 |
-
path = harvester.http_fetch(url, name=name)
|
| 685 |
-
if path:
|
| 686 |
-
self.append_log(f" -> Stored at: {path}")
|
| 687 |
-
else:
|
| 688 |
-
self.append_log(" x Fetch failed")
|
| 689 |
-
|
| 690 |
-
def harvest_index(self):
|
| 691 |
-
p = self.harvest_local.get().strip()
|
| 692 |
-
if not p:
|
| 693 |
-
messagebox.showwarning("Warn", "Enter local path / لطفاً مسیر محلی را وارد کنید")
|
| 694 |
-
return
|
| 695 |
-
path = Path(p).resolve()
|
| 696 |
-
self.append_log(f"[HARVEST] Indexing: {path}")
|
| 697 |
-
files = harvester.index_local([path])
|
| 698 |
-
self.append_log(f" -> Indexed {len(files)} files")
|
| 699 |
-
|
| 700 |
-
def run_worm(self):
|
| 701 |
-
self.append_log("[WORM] Starting self-improvement...")
|
| 702 |
-
worm = CodeLearner(ROOT_DIR)
|
| 703 |
-
worm.improve(self.append_log)
|
| 704 |
-
self.append_log("[WORM] Done.")
|
| 705 |
-
|
| 706 |
-
# ---------------------------
|
| 707 |
-
# Entry
|
| 708 |
-
# ---------------------------
|
| 709 |
-
def main():
|
| 710 |
-
# Optional: load plugins at start if present
|
| 711 |
-
load_plugins()
|
| 712 |
-
root = tk.Tk()
|
| 713 |
-
style = ttk.Style()
|
| 714 |
-
try:
|
| 715 |
-
style.theme_use("clam")
|
| 716 |
-
except Exception:
|
| 717 |
-
pass
|
| 718 |
-
app = App(root)
|
| 719 |
-
root.mainloop()
|
| 720 |
-
|
| 721 |
-
if __name__ == "__main__":
|
| 722 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|