Minimal AI chat

A panelini AI chat answering a question

Note

The capture above and the live demo below both run against a stand-in model that streams one fixed answer: no language model is running and no request leaves your browser. LangChain cannot be installed under Pyodide (langchain-core needs uuid-utils and zstandard, native extensions with no pure-Python wheel), and provider credentials do not belong in a public page. Run the example locally with your own credentials to chat for real.

Source: examples/panels/ai/chat_min.py Test: tests/panels/ai/examples/test_chat_min.py

The shortest path to a working LLM chat inside panelini.

The code

from dotenv import load_dotenv
from panel import serve

from panelini import Panelini

load_dotenv()


def create_app() -> Panelini:
    """Create a fresh app instance (one per browser session)."""
    return Panelini(title="Panelini AI Chat", use_ai=True, show_user=True)


if __name__ == "__main__":
    serve(create_app, title="Panelini AI Chat", port=5006)

Serving the factory (not a single instance) gives every browser session its own app, so conversations stay per user.

What you get

        graph LR
    subgraph dashboard [" Panelini ( use_ai=True ) "]
        direction LR
        subgraph sidebar [" Left sidebar "]
            conv(["Conversations"])
            prov(["Provider Settings"])
            model(["Model Settings"])
            tools(["Basic Tools"])
        end
        subgraph main [" Main area "]
            chat(["Chat"])
        end
    end

    classDef side fill:#6366f1,stroke:#4f46e5,color:#ffffff
    classDef main fill:#0d7377,stroke:#095c5f,color:#ffffff
    class conv,prov,model,tools side
    class chat main
    
  • Left sidebar - two icon tabs: conversations (per-user history as a drag-and-drop folder tree with new chat, folders, import/export, search, inline rename, delete with undo, and a toggle to a date-grouped list view) and setup (provider/model pickers, temperature slider, tool toggles).

  • Main area - the chat window, filling the width. Add ai_show_preview=True for a markdown preview pane next to it that the update_preview tool can write to.

Configuration

panelini reads provider credentials from environment variables. Drop a .env file next to your script:

ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_ENDPOINT=https://api.anthropic.com

The bundled default_config.yml covers Anthropic and Azure OpenAI. Point at a custom config with ai_config_path="config.yml" or the PANELINI_AI_CONFIG_PATH env var.

How the test exercises it

@pytest.fixture(scope="module")
def panel_server(mock_langchain, tmp_path_factory):
    os.environ["PANELINI_HISTORY_DB"] = str(tmp_path_factory.mktemp("history") / "history.sqlite3")
    with warnings.catch_warnings(), config_patch, model_patch:
        module = importlib.reload(importlib.import_module("examples.panels.ai.chat_min"))
        server = pn.serve(module.create_app, port=_PORT, threaded=True, show=False)
        ...

The fixture imports the real example module under a LangChain mock, so a regression in chat_min.py immediately fails the test. See tests/panels/ai/examples/conftest.py for the shared mock_langchain fixture.

Run it live

This example runs entirely in your browser via Pyodide, with the stand-in model described above. The first load downloads packages, so give it a few seconds.

Open fullscreen

See also