Skip to content
Closed
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
90 changes: 90 additions & 0 deletions .github/workflows/codex-selfhost-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Codex Self-Hosted Review

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read
pull-requests: write

concurrency:
group: codex-review-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false

jobs:
codex-review:
if: >-
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, chemflow]
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Ensure Codex CLI is available
run: |
source /home/tengcc/soft/miniconda3/etc/profile.d/conda.sh
conda activate chemflow
export PATH="/home/tengcc/.npm-global/bin:${PATH}"
echo "PATH=${PATH}"
echo "HOME=${HOME}"
which codex
codex --version
codex review --help

- name: Ensure PR base branch is fetched
if: github.event_name == 'pull_request'
run: |
git fetch --no-tags origin "${{ github.base_ref }}:${{ github.base_ref }}" || true

- name: Run Codex review
shell: bash
run: |
set -euo pipefail
source /home/tengcc/soft/miniconda3/etc/profile.d/conda.sh
conda activate chemflow
export PATH="/home/tengcc/.npm-global/bin:${PATH}"

codex review --base "${{ github.base_ref }}" > review.stdout 2> review.stderr

if [ -s review.stdout ]; then
cp review.stdout review.md
else
awk '
BEGIN { capture = 0; block = "" }
/^codex$/ { capture = 1; block = ""; next }
capture { block = block $0 ORS }
END { printf "%s", block }
' review.stderr > review.md
fi

if [ ! -s review.md ]; then
{
echo "Codex review produced no parseable output."
echo
echo "Stderr tail:"
tail -n 120 review.stderr
} > review.md
fi

- name: Add job summary
run: |
cat review.md >> "$GITHUB_STEP_SUMMARY"

- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('review.md', 'utf8').slice(0, 65000);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
27 changes: 21 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ on:
push:
tags:
- "v*"
workflow_dispatch:

jobs:
publish:
build:
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -24,8 +23,24 @@ jobs:
run: python -m build
- name: Check distributions
run: python -m twine check dist/*
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/

publish-pypi:
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: python -m twine upload dist/*
uses: pypa/gh-action-pypi-publish@release/v1
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -20,7 +20,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .[dev]
python -m pip install -e .[dev,notebook]
- name: Run tests
run: pytest
- name: Build package
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ __pycache__/
*.py[cod]
*.so
.pytest_cache/
.ipynb_checkpoints/
.playwright-cli/
.venv/
.venv*/
dist/
build/
*.egg-info/
.coverage
htmlcov/
output/
Untitled*.ipynb
.DS_Store
AGENTS.md
14 changes: 11 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ All notable changes to `chemflow-client` will be documented in this file.

## Unreleased

- Added `DEFAULT_BASE_URL` and made `https://chemcloud.info` the default SDK endpoint for public client entry points.
- Added click-to-toggle atom selection to `Chat3DWidget`, plus `get_selected_atom_indices()` and `clear_selection()`.

## 0.1.0

- Initial public release.
- Added one-shot `chat3d(...)` API.
- Added stateful `ChemFlow3DClient` with one-step local undo.
- Added optional `Chat3DWidget` notebook cell widget.
- Added `DEFAULT_BASE_URL` and made `https://chemcloud.info` the default SDK endpoint for public client entry points.
- Added `CHEMFLOW_API_KEY` / `CHEMFLOW_BASE_URL` environment variable support across the public client entry points.
- Added click-to-toggle atom selection to `Chat3DWidget`, plus `get_selected_atom_indices()` and `clear_selection()`.
- Added an explicit notebook waiting state after `Send`, and changed widget request handling to surface failures in widget UI instead of raising by default.
- Changed widget `Send` handling to run in the background, and added `Chat3DWidget.chat_async(...)` so notebook cells can continue running while the request is in flight.
- Changed the notebook waiting UX from a direct status banner to a chat-inline assistant thinking bubble.
- Fixed notebook async chat completion so background results are scheduled back onto the Jupyter kernel loop instead of updating widget state from the worker thread.
- Allowed `ChemFlow3DClient.start()`, `Chat3DWidget()`, and one-shot `chat3d(atoms=None, ...)` to begin from an empty workspace and generate structure through chat.
- Hardened `Chat3DWidget` message rendering to avoid executing HTML from user prompts or backend responses inside notebooks.
- Lowered the minimum supported Python version from 3.10 to 3.9.
- Updated contributor validation guidance to run the full test suite with notebook extras, and added Python 3.9 to the CI test matrix.
68 changes: 42 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Public Python client for ChemFlow 3D chat editing.

Default public service URL: `https://chemcloud.info`
Supported Python versions: 3.9+

## Install

Expand All @@ -16,7 +16,15 @@ Notebook widget support is optional:
pip install "chemflow-client[notebook]"
```

## One-shot Usage
For contributors who want to run the full test suite:

```bash
pip install -e ".[dev,notebook]"
```

## Usage

Blocking Python API:

```python
from ase.build import molecule
Expand All @@ -26,44 +34,52 @@ atoms = molecule("H2O")
updated_atoms, text = chat3d(
atoms,
"change the H-O-H angle to 110 degrees",
api_key="cfsk_xxx",
)

generated_atoms, text = chat3d(
atoms=None,
prompt="generate methane",
)
```

Override `base_url` only when targeting a self-hosted ChemFlow deployment.
## JupyterLab

## Stateful Usage
Async notebook widget:

```python
from ase.build import molecule
from chemflow_client import ChemFlow3DClient
from chemflow_client import Chat3DWidget

client = ChemFlow3DClient(
api_key="cfsk_xxx",
)
client.start(molecule("NH3"))
widget = Chat3DWidget()
widget
```

atoms, text = client.chat("rotate one hydrogen slightly outward")
atoms = client.undo()
client.close()
```python
widget.get_atoms()
```

## Notebook Widget
![JupyterLab widget demo](docs/assets/chemflow-widget-demo.gif)


## Configure

Create an API key at <https://chemflow.cloud/user-center/api-keys>.

You can configure the client with environment variables:

```bash
export CHEMFLOW_API_KEY="cfsk_xxx"
```

You can also pass configuration as arguments to `chat3d(...)` or `Chat3DWidget(...)`:

```python
from IPython.display import display
from ase.build import molecule
from chemflow_client import Chat3DWidget
from chemflow_client import Chat3DWidget, chat3d

widget = Chat3DWidget(
molecule("CH4"),
updated_atoms, text = chat3d(
atoms=None,
prompt="generate methane",
api_key="cfsk_xxx",
)
display(widget)

latest_atoms = widget.get_atoms()
selected_atoms = widget.get_selected_atom_indices()
widget = Chat3DWidget(api_key="cfsk_xxx")
```

The widget is a cell output widget, not a full JupyterLab sidebar extension.
Clicking atoms in the widget toggles selection with a light-yellow highlight similar to the web viewer.
61 changes: 47 additions & 14 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,69 @@
# Release Process

This repository publishes the `chemflow-client` package directly to PyPI.

## Prerequisites

- Clean git working tree
- PyPI token configured as `TWINE_PASSWORD`
- Username set to `__token__`
- A clean git checkout based on the release commit you want to publish.
- PyPI project `chemflow-client` created on PyPI, or a pending Trusted Publisher configured for the first release.
- A GitHub environment named `pypi`.
- Trusted Publishing configured on PyPI for this GitHub repository.
- Optional but recommended: require manual approval on the `pypi` environment.

## Trusted Publishing Setup

Configure the following publisher in PyPI before the first release:

- Owner: `SingletC`
- Repository: `chemflow-client`
- Workflow filename: `publish.yml` (the workflow file is `.github/workflows/publish.yml`)
- Environment name: `pypi`

The workflow uses `pypa/gh-action-pypi-publish@release/v1` with GitHub OIDC. No long-lived API token is required after Trusted Publishing is configured.

## Local Validation

Run validation from a clean checkout. Do not publish from a working tree that contains local feature changes that are not part of the release.

If a local `build/` directory already exists in the repository root, it can shadow the installed `build` package and break `python -m build`. Remove local build artifacts first, or run the commands in a fresh clone.

```bash
python -m venv .venv
. .venv/bin/activate
pip install -U pip build twine pytest
pip install -e .[dev]
pytest
python -m pip install --upgrade pip
python -m pip install -e .[dev,notebook]
rm -rf build dist *.egg-info
pytest -q
python -m build
python -m twine check dist/*
```

## Version Bump
## Versioning

For a new release:

1. Update `version` in `pyproject.toml`.
2. Add a new section to `CHANGELOG.md`.
3. Commit and tag: `git tag vX.Y.Z`.
2. Promote the relevant notes from `CHANGELOG.md` into a new version section.
3. Commit the release changes.

For the first public release, keep the version at `0.1.0` unless release content changes.

## Publish
## PyPI Release

After local validation succeeds:

```bash
TWINE_USERNAME=__token__ TWINE_PASSWORD=<pypi-token> python -m twine upload dist/*
git push origin dev
git tag v0.1.0
git push origin v0.1.0
```

## GitHub Release
Pushing the version tag triggers the `publish` workflow. The `build` and `publish-pypi` jobs will publish to PyPI.

## Post-release Checks

- Push the version tag.
- The `publish.yml` workflow will build and publish from the tag if repository secrets are configured.
- Confirm the PyPI project page renders correctly.
- Install `chemflow-client==0.1.0` from PyPI in a clean virtual environment.
- Install `"chemflow-client[notebook]==0.1.0"` in a clean virtual environment.
- Verify `from chemflow_client import chat3d, ChemFlow3DClient, Chat3DWidget, DEFAULT_BASE_URL` succeeds.
- Verify the GitHub tag matches the released version.
Binary file added docs/assets/chemflow-widget-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "chemflow-client"
version = "0.1.0"
description = "Public Python client for ChemFlow 3D chat editing"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.9"
license = "MIT"
authors = [
{ name = "ChemFlow" }
Expand All @@ -17,6 +17,7 @@ classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down
Loading