Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f5bebf7
Add Activity on Demand: AI-assisted Sugar activity generation
Ashutoshx7 Jun 23, 2026
d3b1181
codegen: demand full-fledged activities with quality bar checklist
Ashutoshx7 Jun 23, 2026
b9428e0
Add SEARCH/REPLACE refinement: 10-20x cheaper follow-up edits
Ashutoshx7 Jun 23, 2026
db87cd4
Fix 3 critical refinement bugs found in deep audit
Ashutoshx7 Jun 23, 2026
345577d
Preview generated AOD activities live
Ashutoshx7 Jun 23, 2026
b922562
Stream AOD generation and harden previews
Ashutoshx7 Jun 24, 2026
9a39b77
Tune AOD OpenRouter defaults
Ashutoshx7 Jun 24, 2026
e64c074
Use Sonnet for AOD generation testing
Ashutoshx7 Jun 24, 2026
cbb9ed4
Improve AOD prompt input and defaults
Ashutoshx7 Jun 24, 2026
5ec4dbd
Fix bugs, add code size selector, and harden AOD pipeline
Ashutoshx7 Jun 28, 2026
9a04215
Harden AOD backend: cache RAG corpus, backoff retries, fix tempdir leak
Ashutoshx7 Jun 28, 2026
b38cb89
Implement widget-specific click targeting for Live Edit Mode
Ashutoshx7 Jun 28, 2026
c2c9826
Fix preview, improve chat responses, and add activity-specific sideba…
Ashutoshx7 Jun 28, 2026
fa4110b
Fix sidebar visibility and shorten chat bubbles
Ashutoshx7 Jun 28, 2026
227ca16
Add slide animation to sidebar open/close via Gtk.Revealer
Ashutoshx7 Jun 28, 2026
7a8edea
Replace Revealer with manual smoothstep sidebar animation
Ashutoshx7 Jun 28, 2026
cc465cc
Improve create-view UX with constructionist options and visual polish
Ashutoshx7 Jun 28, 2026
3b353af
Simplify provider selector following Sugar design philosophy
Ashutoshx7 Jun 28, 2026
e4c00e5
Remove age/collab groups and model/endpoint boxes
Ashutoshx7 Jun 28, 2026
9bacf20
Fix sidebar animation: use Gtk.Revealer instead of manual set_size_re…
Ashutoshx7 Jun 28, 2026
963f912
Fix generated previews and update OpenRouter model
Ashutoshx7 Jun 29, 2026
199a222
Polish AOD generation UX and harden the pipeline
Ashutoshx7 Jul 3, 2026
5d3842a
Reject imports of runtime-missing optional modules
Ashutoshx7 Jul 4, 2026
cc14c6a
Insert rendering guidance via the prompt's substitution dict
Ashutoshx7 Jul 4, 2026
3c5863b
Add prompt enhancement: expand short ideas into activity briefs
Ashutoshx7 Jul 5, 2026
d170594
Fix live-edit toolbar detection: ToolbarBox is a sugar3 class, not Gtk
Ashutoshx7 Jul 5, 2026
7ec1244
Port generation-quality upgrades from the studio
Ashutoshx7 Jul 5, 2026
8edb36e
Let the model draw each activity's icon
Ashutoshx7 Jul 6, 2026
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
5 changes: 4 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

# E402 module level import not at top of file
# gi.require_version() is required before later imports
# W503/W504 (line break before/after binary operator) are in flake8's
# default ignore list; setting ignore= would otherwise re-enable both,
# and they can never be satisfied at the same time.

ignore = E402
ignore = E402, W503, W504
107 changes: 107 additions & 0 deletions AOD_TEST_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Activity-on-Demand — Quick Test Harness

The Activity-on-Demand (AOD) backend and Home View UI panel are already implemented in this branch.

- Backend modules: `src/jarabe/model/aod*.py`
- UI panel: `src/jarabe/desktop/homebox.py` (`_CreateAIActivityPanel`)
- Toolbar entry point: `src/jarabe/desktop/viewtoolbar.py`

## Run the tests

```bash
PYTHONPATH=src python3 -m pytest tests/test_aod*.py -q
```

All 55 AOD tests should pass.

## Generate an activity from the command line

### Local template (no API key)

```bash
python3 aod_test_cli.py \
--provider local-template \
--prompt "a drawing activity where I can paint colorful shapes"
```

### Gemini

```bash
GEMINI_API_KEY=YOUR_KEY python3 aod_test_cli.py \
--provider gemini \
--model gemini-2.5-flash \
--prompt "a quiz game about animals for young learners"
```

### OpenAI

```bash
OPENAI_API_KEY=YOUR_KEY python3 aod_test_cli.py \
--provider openai \
--model gpt-4.1-mini \
--prompt "a typing practice activity with word bank"
```

### OpenCode Go (Kimi)

```bash
OPENCODE_API_KEY=YOUR_KEY python3 aod_test_cli.py \
--provider opencode-go \
--model kimi-k2.7-code \
--prompt "a fractions playground where teams build models"
```

### Ollama (local)

```bash
AOD_LLM_PROVIDER=ollama AOD_OLLAMA_MODEL=llama3.1 python3 aod_test_cli.py \
--provider ollama \
--prompt "a simple calculator tool for fractions"
```

## Benchmark multiple providers/models

Create a `prompts.txt` file with one prompt per line, then:

```bash
GEMINI_API_KEY=... OPENCODE_API_KEY=... python3 aod_benchmark.py \
--providers gemini,opencode-go \
--models gemini-2.5-flash,kimi-k2.7-code \
--prompts aod_sample_prompts.txt \
--output /tmp/aod_benchmark
```

Results are written to `benchmark.csv` and `benchmark.json` in the output directory.

## Check provider status

```bash
python3 aod_test_cli.py --status
```

## Test from Python directly

```python
import sys
sys.path.insert(0, 'src')

from jarabe.model.aodspec import ActivitySpec, name_from_prompt
from jarabe.model.aodpipeline import generate_activity
from jarabe.model.aodllm import create_provider

spec = ActivitySpec(
name=name_from_prompt("a drawing activity where I can paint colorful shapes"),
prompt="a drawing activity where I can paint colorful shapes",
category="creation",
license_id="GPL-3.0-or-later",
)

provider = create_provider('gemini') # requires GEMINI_API_KEY
result = generate_activity(spec, provider=provider, provider_name='gemini')
print(result.bundle_path)
```

## Notes

- The generated `.xo` bundle is placed under `~/.sugar/default/aod/projects/` by default, or in the directory you pass with `--output`.
- The Home View UI panel can only be fully exercised inside a running Sugar session because it depends on D-Bus, Telepathy, and the full GTK3 desktop stack.
152 changes: 152 additions & 0 deletions aod_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Benchmark Activity-on-Demand across multiple providers/models.

Example:
GEMINI_API_KEY=... OPENCODE_API_KEY=... PYTHONPATH=src python3 \
aod_benchmark.py --providers gemini,opencode-go \
--models gemini-2.5-flash,kimi-k2.7-code \
--prompts prompts.txt --output /tmp/aod_benchmark
"""

import argparse
import csv
import json
import os
import sys
import time

sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))

from jarabe.model.aodspec import ActivitySpec
from jarabe.model.aodspec import name_from_prompt
from jarabe.model.aodpipeline import generate_activity
from jarabe.model.aodpipeline import PipelineError
from jarabe.model.aodllm import create_provider
from jarabe.model.aodllm import ProviderError


def _generate_one(prompt, provider_name, model, output_root):
spec = ActivitySpec(
name=name_from_prompt(prompt),
prompt=prompt,
category='creation',
license_id='GPL-3.0-or-later',
template='auto',
age_band='all',
)
errors = spec.validate()
if errors:
return {'error': 'spec: %s' % '; '.join(errors)}

provider = None
if provider_name != 'local-template':
try:
provider = create_provider(provider_name, model=model)
except ProviderError as error:
return {'error': 'provider: %s' % error}

start = time.time()
try:
result = generate_activity(
spec,
output_root=output_root,
provider=provider,
provider_name=provider_name,
use_rag=True,
)
except PipelineError as error:
return {'error': 'pipeline: %s' % error}
elapsed = time.time() - start

return {
'success': True,
'elapsed': elapsed,
'bundle_id': result.bundle_id,
'project_path': result.project_path,
'bundle_path': result.bundle_path,
'provider': result.provider,
'model': result.model,
'template': result.plan.get('template'),
'code_source': result.plan.get('code_source', 'template'),
'provider_fallback': result.plan.get('provider_fallback_reason', ''),
'codegen_fallback': result.plan.get('codegen_fallback_reason', ''),
'codegen_attempts': result.plan.get('codegen_attempts', 0),
}


def main():
parser = argparse.ArgumentParser(
description='Benchmark AOD across providers.',
)
parser.add_argument('--providers', default='local-template',
help='Comma-separated provider names')
parser.add_argument('--models', default=None,
help='Comma-separated model names (same length as '
'providers, or one shared model)')
parser.add_argument('--prompts', required=True,
help='File with one prompt per line')
parser.add_argument('--output', required=True,
help='Output directory for benchmark results')
parser.add_argument('--csv', default='benchmark.csv',
help='CSV output filename')
parser.add_argument('--json', default='benchmark.json',
help='JSON output filename')

args = parser.parse_args()

providers = [p.strip() for p in args.providers.split(',') if p.strip()]
if args.models:
models = [m.strip() for m in args.models.split(',') if m.strip()]
if len(models) == 1:
models = models * len(providers)
elif len(models) != len(providers):
print("--models must have one value or same count as --providers")
return 1
else:
models = [''] * len(providers)

with open(args.prompts, encoding='utf-8') as f:
prompts = [line.strip() for line in f if line.strip()]

os.makedirs(args.output, exist_ok=True)
rows = []
for provider_name, model in zip(providers, models):
for index, prompt in enumerate(prompts, 1):
run_dir = os.path.join(
args.output,
'%s_%s_prompt%d' % (provider_name, model or 'default', index),
)
os.makedirs(run_dir, exist_ok=True)
print("[%s / %s] prompt %d: %s" % (
provider_name, model or 'default', index, prompt[:60]))
result = _generate_one(prompt, provider_name, model, run_dir)
rows.append({
'provider': provider_name,
'model': model or '',
'prompt_index': index,
'prompt': prompt,
**result,
})
status = 'OK' if result.get('success') else 'FAIL'
print(" -> %s (%.1fs)" % (
status, result.get('elapsed', 0.0)))

csv_path = os.path.join(args.output, args.csv)
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)

json_path = os.path.join(args.output, args.json)
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(rows, f, indent=2)

success = sum(1 for r in rows if r.get('success'))
print("\nBenchmark complete: %d/%d succeeded" % (success, len(rows)))
print("CSV: %s" % csv_path)
print("JSON: %s" % json_path)
return 0 if success == len(rows) else 1


if __name__ == '__main__':
sys.exit(main())
Loading