> ## Documentation Index
> Fetch the complete documentation index at: https://metacognition-fdc534de-master.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Streamlit chat UI

> Build one Streamlit page that recalls from Tex, streams GPT output, and shows the memories used.

This recipe builds a small browser demo. Streamlit can rerun the script often, but Tex keeps the long-term memory. `st.session_state` only stores UI state for the current browser session.

<Steps>
  <Step title="Install packages">
    ```bash theme={null}
    pip install tex-sdk streamlit openai
    ```
  </Step>

  <Step title="Bootstrap clients once">
    Wrap `Tex` and `OpenAI` in `@st.cache_resource` so Streamlit does not recreate clients on every interaction:

    ```python theme={null}
    import os
    import streamlit as st
    from openai import OpenAI
    from tex import Tex

    st.set_page_config(page_title="Tex chat", page_icon="🧠")

    @st.cache_resource
    def get_clients():
        tex = Tex(
            api_key=os.environ["TEX_API_KEY"],
            base_url=os.environ.get("TEX_BASE_URL", "https://api.getmetacognition.com"),
        )
        gpt = OpenAI()
        return tex, gpt

    tex, gpt = get_clients()
    ```
  </Step>

  <Step title="Pin a session id per browser tab">
    Store a stable `sid` in session state. You can also read `?uid=` from the query string to test different users:

    ```python theme={null}
    if "sid" not in st.session_state:
        st.session_state.sid = f"web-{st.query_params.get('uid', 'anon')}-default"
    sid = st.session_state.sid

    if "messages" not in st.session_state:
        st.session_state.messages = []
    ```
  </Step>

  <Step title="Show usage in the sidebar">
    Call `tex.usage.today()` to show current quota usage during demos:

    ```python theme={null}
    with st.sidebar:
        st.write("## Usage today")
        today = tex.usage.today()
        pct_in  = min(1.0, today.tokens_in_used  / max(1, today.tokens_in_limit))
        pct_out = min(1.0, today.tokens_out_used / max(1, today.tokens_out_limit))
        st.progress(pct_in,  f"in:  {today.tokens_in_used:,} / {today.tokens_in_limit:,}")
        st.progress(pct_out, f"out: {today.tokens_out_used:,} / {today.tokens_out_limit:,}")
        st.caption(f"session: `{sid}`")
    ```
  </Step>

  <Step title="Render chat + handle input">
    Render messages from `st.session_state.messages`, then handle `st.chat_input`:

    ```python theme={null}
    for m in st.session_state.messages:
        with st.chat_message(m["role"]):
            st.write(m["text"])

    if prompt := st.chat_input("Talk to me…"):
        import datetime
        now = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")

        with st.chat_message("user"):
            st.write(prompt)

        with st.chat_message("assistant"):
            with st.spinner("Recalling…"):
                hits = tex.recall(q=prompt, session_id=sid, top_k=5)

            if hits.hits.turns:
                st.caption(f"confidence {hits.confidence:.2f}")
                with st.expander("Memory used"):
                    for h in hits.hits.turns:
                        st.write(f"`{h.score:.2f}` — {h.text}")

            memory_block = "\n".join(f"- {h.text}" for h in hits.hits.turns)
            sys_prompt = f"You are a thoughtful assistant. Memory:\n{memory_block}"

            chat = gpt.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": sys_prompt},
                    {"role": "user",   "content": prompt},
                ],
                stream=True,
            )
            answer = st.write_stream(
                chunk.choices[0].delta.content or "" for chunk in chat
            )

        tex.conversations.remember(
            session_id=sid,
            turns=[
                {"role": "user", "text": prompt, "timestamp": now},
                {"role": "assistant", "text": answer, "timestamp": now},
            ],
        )
        st.session_state.messages += [
            {"role": "user", "text": prompt},
            {"role": "assistant", "text": answer},
        ]
    ```
  </Step>

  <Step title="Run the app">
    ```bash theme={null}
    export TEX_API_KEY=tex_live_...
    export OPENAI_API_KEY=sk-...
    streamlit run app.py
    ```

    Open `http://localhost:8501/?uid=alice` and `?uid=bob` in two tabs to test isolation.
  </Step>
</Steps>

## One file

If you want one block to paste, use this full `app.py`:

```python app.py theme={null}
import os
import datetime
import streamlit as st
from openai import OpenAI
from tex import Tex

st.set_page_config(page_title="Tex chat", page_icon="🧠")

@st.cache_resource
def get_clients():
    tex = Tex(
        api_key=os.environ["TEX_API_KEY"],
        base_url=os.environ.get("TEX_BASE_URL", "https://api.getmetacognition.com"),
    )
    gpt = OpenAI()
    return tex, gpt

tex, gpt = get_clients()

if "sid" not in st.session_state:
    st.session_state.sid = f"web-{st.query_params.get('uid', 'anon')}-default"
sid = st.session_state.sid

if "messages" not in st.session_state:
    st.session_state.messages = []

with st.sidebar:
    st.write("## Usage today")
    today = tex.usage.today()
    pct_in  = min(1.0, today.tokens_in_used  / max(1, today.tokens_in_limit))
    pct_out = min(1.0, today.tokens_out_used / max(1, today.tokens_out_limit))
    st.progress(pct_in,  f"in:  {today.tokens_in_used:,} / {today.tokens_in_limit:,}")
    st.progress(pct_out, f"out: {today.tokens_out_used:,} / {today.tokens_out_limit:,}")
    st.caption(f"session: `{sid}`")

for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["text"])

if prompt := st.chat_input("Talk to me…"):
    now = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")

    with st.chat_message("user"):
        st.write(prompt)

    with st.chat_message("assistant"):
        with st.spinner("Recalling…"):
            hits = tex.recall(q=prompt, session_id=sid, top_k=5)

        if hits.hits.turns:
            st.caption(f"confidence {hits.confidence:.2f}")
            with st.expander("Memory used"):
                for h in hits.hits.turns:
                    st.write(f"`{h.score:.2f}` — {h.text}")

        memory_block = "\n".join(f"- {h.text}" for h in hits.hits.turns)
        sys_prompt = f"You are a thoughtful assistant. Memory:\n{memory_block}"

        chat = gpt.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": sys_prompt},
                {"role": "user",   "content": prompt},
            ],
            stream=True,
        )
        answer = st.write_stream(chunk.choices[0].delta.content or "" for chunk in chat)

    tex.conversations.remember(
        session_id=sid,
        turns=[
            {"role": "user", "text": prompt, "timestamp": now},
            {"role": "assistant", "text": answer, "timestamp": now},
        ],
    )
    st.session_state.messages += [
        {"role": "user", "text": prompt},
        {"role": "assistant", "text": answer},
    ]
```

## Why Streamlit + Tex

This pattern pairs a disposable browser UI with durable memory in Tex.

* **Refreshes keep memory:** Streamlit session state can reset; Tex memory stays.
* **No Redis or Postgres for demos:** Tex stores the long-term memory.
* **Visible recall:** the expander shows exactly what the model saw.
