"""Ananke v1 — public demo (AMEFORGE).
This file only contains the user interface. The Ananke engine and its memory
are loaded at startup from a private AMEFORGE repository.
"""
import html as html_lib
import importlib.util
import os
import gradio as gr
from huggingface_hub import hf_hub_download
PRIVATE_REPO = "AMFORGE/ananke-core"
TOKEN = os.environ.get("HF_TOKEN")
_engine_path = hf_hub_download(PRIVATE_REPO, "ananke_core.py", token=TOKEN)
_memory_path = hf_hub_download(PRIVATE_REPO, "memory.pkl.gz", token=TOKEN)
_spec = importlib.util.spec_from_file_location("ananke_core", _engine_path)
engine = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(engine)
# v1 : the answer is chosen by a learned reader (loaded privately), not by hand-written rules
_reader_path = hf_hub_download(PRIVATE_REPO, "reader.py", token=TOKEN)
_rspec = importlib.util.spec_from_file_location("reader", _reader_path)
reader_mod = importlib.util.module_from_spec(_rspec)
_rspec.loader.exec_module(reader_mod)
READER = reader_mod.Reader([hf_hub_download(PRIVATE_REPO, f"reader_s{i}.pt", token=TOKEN) for i in (0, 1)])
BASE = engine.Ananke.load(_memory_path)
SOURCES = sorted({f[3] for f in BASE.facts if f[3]})
S = BASE.stats()
EXAMPLES = [
"What is the capital of Togo?",
"What is the official language of Cape Verde?",
"When did Ghana become independent?",
"Who was the first president of Senegal?",
"What is the largest city of Nigeria?",
"Who is Kwame Nkrumah?",
"What is the capital of Atlantis?",
]
def _answer(mem, question):
"""memory finds the sentences; the learned reader picks the answer in them"""
a0, hits = mem.ask(question, k=5)
if a0 is None and not hits:
return None, []
if a0 == getattr(engine, "DEFINITION", None) or not hits:
return a0, hits[:3]
ent, rel = mem.find_entity(question)
if ent is not None and not rel: # "who is X?": show what memory holds about X
return a0, hits[:3]
a, sent = READER.answer(question, hits)
if a is None:
return a0, hits[:3]
shown = [h for h in hits if h[1] == sent] + [h for h in hits if h[1] != sent]
return a, shown[:3]
def esc(t):
return html_lib.escape(t or "")
def _card(answer, hits, where):
if answer is None and not hits:
return ("""
I don't know
"""
"""
Ananke has not read anything that answers this question. """
"""It refuses rather than inventing an answer.
"""
"""
Teach it: paste a text or a Wikipedia title in the Teach it tab.
""")
if answer == getattr(engine, "DEFINITION", None):
head, cls = "Here is what it read about it", "defn"
elif answer:
head, cls = esc(answer.title()), "ans"
else:
head, cls = "No short answer — here is what it read", "ans"
quotes = "".join(
f'{esc(sentence)}
'
f'
source: {esc(source or "your text")} '
for _, sentence, _, source in hits)
return (f'{head}
'
f'
Found in {esc(where)}.
'
f'
{quotes}
')
def ask(question, personal):
question = (question or "").strip()
if not question:
return 'Type a question above.
', personal
if personal is not None:
a, hits = _answer(personal, question)
if a is not None or hits:
return _card(a, hits, "what you taught it"), personal
a, hits = _answer(BASE, question)
return _card(a, hits, "its general memory"), personal
def teach(text, title, personal):
personal = personal if personal is not None else engine.Ananke()
msgs = []
if title and title.strip():
try:
n, r = personal.read(engine.fetch_wikipedia(title.strip()), source=f"Wikipedia: {title.strip()}")
msgs.append(f"{esc(title.strip())} — read {n} new facts.")
except Exception as e:
msgs.append(f"Could not read {esc(title.strip())} ({esc(str(e))}).")
if text and text.strip():
n, r = personal.read(text, source="your text")
msgs.append(f"Your text — read {n} new facts, {r} reinforced.")
if not msgs:
return 'Paste a text or give a Wikipedia title.
', personal
s = personal.stats()
body = "".join(f"{m}" for m in msgs)
return (f'Learned.
'
f'
Your personal memory: {s["faits"]} facts, {s["mots_lus"]} words read. '
f'Private to your session — it disappears when you leave.
', personal)
def forget(_):
return 'Your personal memory has been erased.
', None
THEME = gr.themes.Soft(
primary_hue=gr.themes.colors.indigo,
secondary_hue=gr.themes.colors.amber,
neutral_hue=gr.themes.colors.slate,
)
CSS = """
.gradio-container { max-width: 940px !important; }
.hero { background: linear-gradient(135deg, #1e1b4b 0%, #3730a3 55%, #92400e 130%);
border-radius: 22px; padding: 38px 34px 30px; color: #fff; margin-bottom: 20px;
box-shadow: 0 12px 40px rgba(30,27,75,.35); }
.hero .kicker { display: inline-block; font-size: 12px; letter-spacing: 2.5px; text-transform: uppercase;
color: #fcd34d; border: 1px solid rgba(252,211,77,.5); border-radius: 999px;
padding: 5px 14px; margin-bottom: 14px; }
.hero h1 { font-size: 44px; margin: 0 0 6px; font-weight: 800; letter-spacing: -1px; }
.hero h1 .v1 { background: #fcd34d; color: #1e1b4b; font-size: 20px; border-radius: 10px;
padding: 3px 10px; vertical-align: middle; margin-left: 8px; letter-spacing: 0; }
.hero .tagline { font-size: 18px; opacity: .92; margin: 0 0 18px; }
.hero .pills { display: flex; flex-wrap: wrap; gap: 10px; }
.hero .pill { background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.22);
border-radius: 999px; padding: 6px 15px; font-size: 13.5px; }
.hero .pill b { color: #fcd34d; }
.chips { flex-wrap: wrap !important; gap: 8px !important; margin: 10px 0 4px; }
.chip { border-radius: 999px !important; font-size: 13px !important; padding: 7px 14px !important;
background: #eef2ff !important; border: 1px solid #c7d2fe !important; color: #3730a3 !important;
flex: 0 1 auto !important; }
.chip:hover { background: #e0e7ff !important; }
.ask-btn { border-radius: 14px !important; font-size: 17px !important; padding: 12px !important; }
.big-q textarea { font-size: 16px !important; border-radius: 14px !important; }
.card { border-radius: 18px; padding: 24px 26px; margin-top: 16px;
background: #fff; border: 1px solid #e2e8f0; box-shadow: 0 6px 24px rgba(15,23,42,.07); }
.card.ans { border-top: 5px solid #4f46e5; }
.card.defn { border-top: 5px solid #d97706; }
.card.idk { border-top: 5px solid #94a3b8; background: #f8fafc; }
.card.teach { border-top: 5px solid #059669; }
.card.empty { color: #64748b; }
.headline { font-size: 30px; font-weight: 800; color: #1e1b4b; margin-bottom: 4px; letter-spacing: -.5px; }
.idk-title { font-size: 26px; font-weight: 800; color: #475569; margin-bottom: 8px; }
.where { color: #64748b; font-size: 14px; margin-bottom: 14px; }
.hint { color: #64748b; font-size: 13.5px; margin-top: 10px; }
.quotes { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
.quote { background: #f1f5f9; border-left: 4px solid #818cf8; border-radius: 0 12px 12px 0;
padding: 12px 16px; }
.quote p { margin: 0 0 6px; color: #1e293b; font-size: 15px; line-height: 1.55; }
.quote .src { font-size: 12.5px; color: #64748b; }
.card ul { margin: 8px 0; padding-left: 20px; color: #1e293b; }
.src-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.src-pills span { background: #eef2ff; border: 1px solid #c7d2fe; color: #3730a3;
border-radius: 999px; padding: 5px 13px; font-size: 13px; }
.footer { text-align: center; color: #94a3b8; font-size: 13px; margin: 26px 0 10px; }
.footer a { color: #4f46e5; }
"""
HERO = f"""
AMEFORGE · Research prototype
Ananke v1
A memory that learns by reading — no hand-written answer rules.
{len(SOURCES)} Wikipedia articles read
{S['faits']:,} facts
{S['mots_lus']:,} words
runs on CPU
"""
FOOTER = """"""
with gr.Blocks(title="Ananke v1 — AMEFORGE", theme=THEME, css=CSS) as demo:
personal = gr.State(None)
gr.HTML(HERO)
with gr.Tab("Ask"):
q = gr.Textbox(label="Your question", placeholder="What is the capital of Togo?",
elem_classes=["big-q"])
with gr.Row(elem_classes=["chips"]):
for ex in EXAMPLES:
b = gr.Button(ex, size="sm", elem_classes=["chip"])
b.click(lambda x=ex: x, outputs=q)
btn = gr.Button("Ask", variant="primary", elem_classes=["ask-btn"])
out = gr.HTML()
btn.click(ask, [q, personal], [out, personal])
q.submit(ask, [q, personal], [out, personal])
with gr.Tab("Teach it"):
gr.Markdown("Give Ananke something new to read. It learns it **in one reading**, "
"then you can ask about it in the *Ask* tab.")
title = gr.Textbox(label="A Wikipedia article title (English)", placeholder="Lomé")
text = gr.Textbox(label="…or paste your own text", lines=8)
with gr.Row():
tbtn = gr.Button("Read it", variant="primary")
fbtn = gr.Button("Forget what I taught")
tout = gr.HTML()
tbtn.click(teach, [text, title, personal], [tout, personal])
fbtn.click(forget, [personal], [tout, personal])
with gr.Tab("What it has read"):
gr.HTML('' + "".join(f"{esc(s)}" for s in SOURCES) + "
")
gr.HTML(FOOTER)
if __name__ == "__main__":
demo.launch()