Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/lib-jims-widget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This file was automatically generated by uv-workspace-codegen
# For more information, see: https://github.com/epoch8/uv-workspace-codegen/blob/master/README.md
# Do not edit this file manually - changes will be overwritten

name: Test lib jims-widget

on:
pull_request:
paths:
- "libs/jims-widget/**"
push:
branches: [master]
paths:
- "libs/jims-widget/**"
- "pyproject.toml"
- "uv.lock"

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
lfs: true

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install the latest version of uv
uses: astral-sh/setup-uv@v6

- name: Install
shell: bash
run: |
cd libs/jims-widget
uv sync --all-groups
- name: Run tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
shell: bash
run: |
uv run pytest libs/jims-widget/
- name: Check formatting
shell: bash
run: |
uv run ruff check libs/jims-widget
- name: Check typing
shell: bash
run: |
uv run mypy -p jims_widget
72 changes: 72 additions & 0 deletions .github/workflows/publish-jims-widget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# This file was automatically generated by uv-workspace-codegen
# For more information, see: https://github.com/epoch8/uv-workspace-codegen/blob/master/README.md
# Do not edit this file manually - changes will be overwritten

name: Publish jims-widget

on:
workflow_dispatch:
push:
tags:
- 'jims-*'

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Install uv
uses: astral-sh/setup-uv@v7

- name: Build wheel
run: uv build
working-directory: libs/jims-widget

- name: Store the distribution packages
uses: actions/upload-artifact@v6
with:
name: python-package-distributions
path: dist/

publish-to-test-pypi:
environment:
name: testpypi
url: https://test.pypi.org/project/jims-widget/
permissions:
id-token: write
runs-on: ubuntu-latest
needs: build

steps:
- uses: actions/download-artifact@v7
with:
name: python-package-distributions
path: dist/

- name: Publish distribution to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

publish-to-pypi:
environment:
name: pypi
url: https://pypi.org/project/jims-widget/
permissions:
id-token: write
runs-on: ubuntu-latest
needs:
- build
- publish-to-test-pypi

steps:
- uses: actions/download-artifact@v7
with:
name: python-package-distributions
path: dist/

- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ build_jims_telegram:

build_jims_tui:
uv build libs/jims-tui

build_jims_widget:
uv build libs/jims-widget

build_vedana_core:
uv build libs/vedana-core
Expand All @@ -23,7 +26,7 @@ build_vedana_etl:
build_vedana_backoffice:
uv build libs/vedana-backoffice

build: build_jims_backoffice build_jims_core build_jims_api build_jims_telegram build_jims_tui build_vedana_core build_vedana_etl build_vedana_backoffice
build: build_jims_backoffice build_jims_core build_jims_api build_jims_telegram build_jims_tui build_jims_widget build_vedana_core build_vedana_etl build_vedana_backoffice

build-vedana-project:
cd apps/vedana && make build
Expand Down
13 changes: 13 additions & 0 deletions apps/vedana/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ services:
db:
condition: service_healthy

widget:
<<: *app-common
ports:
- "8090:8090"
command: uv run python -m jims_widget.main --app vedana_core.app:app --host 0.0.0.0 --port 8090
depends_on:
db-migrate:
condition: service_completed_successfully
memgraph:
condition: service_started
db:
condition: service_healthy

memgraph:
image: memgraph/memgraph-mage
ports:
Expand Down
1 change: 1 addition & 0 deletions apps/vedana/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies = [
"google-cloud-iam >= 2.19.1",
"jims-core>=0.5.1",
"jims-api>=0.5.1",
"jims-widget>=0.5.1",
"vedana-backoffice",
"vedana-etl",
"jims-tui",
Expand Down
45 changes: 45 additions & 0 deletions libs/jims-widget/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# JIMS DeepChat Widget

[DeepChat](https://deepchat.dev/)-based embeddable chat widget for JimsApp backends.

## Quick start

**1. Run the widget server** (point it at your JimsApp):

```bash
uv run jims-widget --app my_app:app --port 8090 --cors-origins "*"
```

**2. Embed on any page** — add one script tag:

```html
<script
src="https://YOUR_HOST/static/jims-widget.js"
data-server="https://YOUR_HOST"
></script>
```

Optional attributes on the `<script>` tag:

| Attribute | Required | Default | Description |
|------------------|----------|-------------------|-------------------------------------|
| `data-server` | yes | — | Widget backend origin (same host as script) |
| `data-contact-id`| no | (anonymous) | Persistent visitor identifier |
| `data-thread-id` | no | (new thread) | Resume an existing thread |
| `data-position` | no | `bottom-right` | `bottom-right` \| `bottom-left` |
| `data-open` | no | `false` | `true` to start with panel open |
| `data-title` | no | Chat assistant | Header title text |
| `data-accent` | no | `#4f46e5` | Accent colour (hex) |

## Use as a library

Mount the widget app in your own FastAPI (or ASGI) app:

```python
from jims_widget import create_widget_app

widget_app = create_widget_app(my_jims_app, cors_origins=["https://example.com"])
# mount or include widget_app as needed
```

A demo page is served at `/` when running the standalone server.
57 changes: 57 additions & 0 deletions libs/jims-widget/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[project]
name = "jims-widget"
dynamic = ["version"]
description = "Embeddable DeepChat-based widget for Jims applications"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"click>=8.0",
"fastapi>=0.128.0",
"jims-core>=0.5.1",
"loguru>=0.7.3",
"uvicorn[standard]>=0.35.0",
]

[tool.uv.sources]
jims-core = { workspace = true }

[tool.uv-workspace-codegen]
generate = true
template_type = ["lib", "publish"]
generate_standard_pytest_step = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]

[dependency-groups]
dev = [
"aiosqlite>=0.20.0",
"httpx>=0.28.0",
"mypy>=1.19.1",
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",
"ruff>=0.15.0",
]

[project.scripts]
jims-widget = "jims_widget.main:main"

[build-system]
requires = ["hatchling", "uv-dynamic-versioning"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/jims_widget"]

[tool.hatch.build.targets.wheel.force-include]
"src/jims_widget/static" = "jims_widget/static"

[tool.hatch.version]
source = "uv-dynamic-versioning"

[tool.uv-dynamic-versioning]
enable = true
vcs = "git"
pattern = "default-unprefixed"
pattern-prefix = "jims-widget-"
3 changes: 3 additions & 0 deletions libs/jims-widget/src/jims_widget/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .server import create_widget_app

__all__ = ["create_widget_app"]
72 changes: 72 additions & 0 deletions libs/jims-widget/src/jims_widget/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import asyncio
from typing import Awaitable

import click
import uvicorn
from jims_core.app import JimsApp
from jims_core.util import (
load_jims_app,
setup_monitoring_and_tracing_with_sentry,
setup_prometheus_metrics,
setup_verbose_logging,
)
from loguru import logger

from jims_widget.server import create_widget_app


async def _resolve_jims_app(app_name: str) -> JimsApp:
loaded_app = load_jims_app(app_name)
if isinstance(loaded_app, Awaitable):
loaded_app = await loaded_app
return loaded_app


@click.command()
@click.option("--app", type=click.STRING, default="app", help="JIMS app in module:attr format.")
@click.option("--host", type=click.STRING, default="0.0.0.0")
@click.option("--port", type=click.INT, default=8090)
@click.option("--cors-origins", type=click.STRING, default="*", help="Comma-separated CORS origins, or * for all.")
@click.option("--enable-sentry", is_flag=True, help="Enable tracing to Sentry", default=False)
@click.option("--metrics-port", type=click.INT, default=8001)
@click.option("--verbose", is_flag=True, default=False)
def cli(
app: str,
host: str,
port: int,
cors_origins: str,
enable_sentry: bool,
metrics_port: int,
verbose: bool,
) -> None:
if verbose:
setup_verbose_logging()

setup_prometheus_metrics(port=metrics_port)

if enable_sentry:
setup_monitoring_and_tracing_with_sentry()

origins = [o.strip() for o in cors_origins.split(",") if o.strip()]

async def run() -> None:
jims_app = await _resolve_jims_app(app)
widget_app = create_widget_app(jims_app, cors_origins=origins)
server = uvicorn.Server(uvicorn.Config(widget_app, host=host, port=port, log_level="info"))
await server.serve()

try:
asyncio.run(run())
except KeyboardInterrupt:
logger.info("Widget server stopped by user")
except Exception as exc:
logger.exception(f"Widget server crashed: {exc}")
raise SystemExit(1) from exc


def main() -> None:
cli(auto_envvar_prefix="JIMS")


if __name__ == "__main__":
main()
Empty file.
Loading