Spaces:
Sleeping
Sleeping
Sandra Sanchez commited on
Commit ·
ccc1b00
1
Parent(s): da080e4
Initial version with just scenarios to push into sxpaces
Browse files- .env.example +1 -0
- .gitignore +20 -0
- .python-version +1 -0
- app/app.py +127 -0
- mcp_server/server.py +39 -0
- mcp_server/templates/dentist.json +8 -0
- mcp_server/templates/doctor.json +5 -0
- mcp_server/templates/first_day_school.json +5 -0
- mcp_server/templates/haircut.json +5 -0
- mcp_server/templates/loud_events.json +5 -0
- mcp_server/templates/new_food.json +5 -0
- mcp_server/templates/new_place.json +5 -0
- mcp_server/templates/physical_education.json +5 -0
- mcp_server/templates/pickup_from_school.json +5 -0
- mcp_server/templates/social_situations.json +5 -0
- pyproject.toml +17 -0
- requirements.txt +4 -0
- uv.lock +0 -0
.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
OPENAI_API_KEY='key'
|
.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python-generated files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[oc]
|
| 4 |
+
build/
|
| 5 |
+
dist/
|
| 6 |
+
wheels/
|
| 7 |
+
*.egg-info
|
| 8 |
+
|
| 9 |
+
# System junk
|
| 10 |
+
.DS_Store
|
| 11 |
+
|
| 12 |
+
# Virtual environments
|
| 13 |
+
.venv
|
| 14 |
+
.env
|
| 15 |
+
venv/
|
| 16 |
+
|
| 17 |
+
# Generated images
|
| 18 |
+
generated_images/
|
| 19 |
+
images/
|
| 20 |
+
*.png
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.11
|
app/app.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
import os
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
import base64
|
| 8 |
+
import requests
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Load environment variables from .env file
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
| 16 |
+
|
| 17 |
+
client = OpenAI(api_key=OPENAI_API_KEY)
|
| 18 |
+
models = client.models.list()
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "mcp_server" / "templates"
|
| 22 |
+
GENERATED_IMAGES_DIR = Path(__file__).resolve().parent.parent / "generated_images"
|
| 23 |
+
GENERATED_IMAGES_DIR.mkdir(exist_ok=True)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_scenarios():
|
| 27 |
+
return [p.stem for p in TEMPLATES_DIR.glob("*.json")]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def generate_story(scenario_name: str):
|
| 31 |
+
filepath = TEMPLATES_DIR / f"{scenario_name}.json"
|
| 32 |
+
if not filepath.exists():
|
| 33 |
+
return "Template not found."
|
| 34 |
+
template = json.loads(filepath.read_text())
|
| 35 |
+
story_prompt = (
|
| 36 |
+
"Rewrite the following scenario into a simple, autism-friendly social story.\n"
|
| 37 |
+
"Use short sentences, gentle tone, clear structure, and supportive language.\n"
|
| 38 |
+
"Do NOT return JSON. Return only the story text.\n\n"
|
| 39 |
+
f"Scenario data:\n{json.dumps(template, indent=2)}"
|
| 40 |
+
)
|
| 41 |
+
story_resp = client.chat.completions.create(
|
| 42 |
+
model="gpt-4o-mini",
|
| 43 |
+
messages=[{"role": "user", "content": story_prompt}]
|
| 44 |
+
)
|
| 45 |
+
return story_resp.choices[0].message.content.strip()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def generate_image(scenario_name: str):
|
| 49 |
+
filepath = TEMPLATES_DIR / f"{scenario_name}.json"
|
| 50 |
+
if not filepath.exists():
|
| 51 |
+
return None
|
| 52 |
+
template = json.loads(filepath.read_text())
|
| 53 |
+
image_prompt = (
|
| 54 |
+
f"Create a soft, calming illustration for a social story about: {template['title']}."
|
| 55 |
+
" Use pastel colors, simple shapes, friendly characters, minimal details, no text."
|
| 56 |
+
)
|
| 57 |
+
# Comment out image generation while developing
|
| 58 |
+
# img = client.images.generate(
|
| 59 |
+
# model="gpt-image-1",
|
| 60 |
+
# prompt=image_prompt,
|
| 61 |
+
# output_format="png",
|
| 62 |
+
# size="512x512"
|
| 63 |
+
# )
|
| 64 |
+
# image_bytes = base64.b64decode(img.data[0].b64_json)
|
| 65 |
+
# timestamp = int(time.time())
|
| 66 |
+
# image_filename = f"{scenario_name}_{timestamp}.png"
|
| 67 |
+
# image_path = GENERATED_IMAGES_DIR / image_filename
|
| 68 |
+
# with open(image_path, "wb") as f:
|
| 69 |
+
# f.write(image_bytes)
|
| 70 |
+
# img_src = str(image_path)
|
| 71 |
+
# return img_src
|
| 72 |
+
# For now, return None or a public image URL
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# def main():
|
| 77 |
+
# print("Loading scenarios...")
|
| 78 |
+
# scenarios = load_scenarios()
|
| 79 |
+
# print("Scenarios loaded:", scenarios)
|
| 80 |
+
#
|
| 81 |
+
# with gr.Blocks() as demo:
|
| 82 |
+
# gr.Markdown("# 🧸 Comfortool\n### Autism-friendly social stories with calming illustrations")
|
| 83 |
+
#
|
| 84 |
+
# dropdown = gr.Dropdown(
|
| 85 |
+
# choices=scenarios,
|
| 86 |
+
# label="Choose a scenario"
|
| 87 |
+
# )
|
| 88 |
+
#
|
| 89 |
+
# generate_btn = gr.Button("Generate Social Story")
|
| 90 |
+
# story_out = gr.Textbox(label="Story", lines=12)
|
| 91 |
+
# image_out = gr.Image(label="Illustration")
|
| 92 |
+
#
|
| 93 |
+
# def on_generate(scenario_name):
|
| 94 |
+
# print("Generating story for:", scenario_name)
|
| 95 |
+
# story = generate_story(scenario_name)
|
| 96 |
+
# print("Story generated:", story)
|
| 97 |
+
# image = None # For now, do not generate image
|
| 98 |
+
# return story, image
|
| 99 |
+
#
|
| 100 |
+
# generate_btn.click(
|
| 101 |
+
# fn=on_generate,
|
| 102 |
+
# inputs=dropdown,
|
| 103 |
+
# outputs=[story_out, image_out]
|
| 104 |
+
# )
|
| 105 |
+
#
|
| 106 |
+
# print("Gradio app initialized.")
|
| 107 |
+
# return demo
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def show_selected(scenario_name):
|
| 111 |
+
return f"You selected: {scenario_name}"
|
| 112 |
+
|
| 113 |
+
def main():
|
| 114 |
+
scenarios = load_scenarios()
|
| 115 |
+
with gr.Blocks() as demo:
|
| 116 |
+
gr.Markdown("# 🧸 Comfortool\n### Available scenarios")
|
| 117 |
+
dropdown = gr.Dropdown(choices=scenarios, label="Choose a scenario")
|
| 118 |
+
output = gr.Textbox(label="Selected scenario")
|
| 119 |
+
dropdown.change(fn=show_selected, inputs=dropdown, outputs=output)
|
| 120 |
+
return demo
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
demo = main()
|
| 125 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
| 126 |
+
|
| 127 |
+
|
mcp_server/server.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
# Initialize MCP server
|
| 6 |
+
mcp = FastMCP("comfortool-mcp-server")
|
| 7 |
+
|
| 8 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 9 |
+
TEMPLATE_DIR = BASE_DIR / "templates"
|
| 10 |
+
IMAGES_DIR = BASE_DIR / "images"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# ---- Tool: Get list of scenarios ----
|
| 14 |
+
@mcp.tool()
|
| 15 |
+
def get_scenarios() -> list[str]:
|
| 16 |
+
"""Return the list of available scenario template names."""
|
| 17 |
+
return [p.stem for p in TEMPLATE_DIR.glob("*.json")]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ---- Tool: Get a specific scenario template ----
|
| 21 |
+
@mcp.tool()
|
| 22 |
+
def get_template(name: str) -> dict:
|
| 23 |
+
"""Load and return a scenario JSON template by name."""
|
| 24 |
+
filepath = TEMPLATE_DIR / f"{name}.json"
|
| 25 |
+
if not filepath.exists():
|
| 26 |
+
raise FileNotFoundError(f"Template '{name}' not found.")
|
| 27 |
+
return json.loads(filepath.read_text())
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ---- Tool: List icons/images (optional for now) ----
|
| 31 |
+
@mcp.tool()
|
| 32 |
+
def get_static_images() -> list[str]:
|
| 33 |
+
"""Return the filenames of static images/icons."""
|
| 34 |
+
return [p.name for p in IMAGES_DIR.glob("*")]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# ---- Run the server ----
|
| 38 |
+
if __name__ == "__main__":
|
| 39 |
+
mcp.run()
|
mcp_server/templates/dentist.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "going_to_the_dentist",
|
| 3 |
+
"title": "Going to the Dentist",
|
| 4 |
+
"story": "Today I am going to the dentist. The dentist helps keep my teeth clean and healthy. First, I will enter the quiet waiting room. Then, someone will call my name. I will sit in a big chair that goes up and down. The dentist will look at my teeth and might clean them. Some sounds may be loud, but they are safe. If I need a break, I can ask for one. When it is finished, I can go home feeling proud.",
|
| 5 |
+
"sensory": ["bright light", "whirring sounds"],
|
| 6 |
+
"tools": ["headphones", "fidget", "sunglasses"],
|
| 7 |
+
"emotion_support": "It is okay to feel nervous. Many people do."
|
| 8 |
+
}
|
mcp_server/templates/doctor.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "going_to_the_doctor",
|
| 3 |
+
"title": "Going to the Doctor",
|
| 4 |
+
"story": "Today I am visiting the doctor. The doctor keeps my body healthy. I will check in and wait until my name is called. The doctor may ask questions and gently check parts of my body. Sometimes, the doctor might use tools like a stethoscope. If I need a moment, I can say 'please wait.' After the visit, I can go home knowing I took good care of myself."
|
| 5 |
+
}
|
mcp_server/templates/first_day_school.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "first_day_of_school",
|
| 3 |
+
"title": "First Day of School",
|
| 4 |
+
"story": "Today is my first day of school. I will meet teachers and classmates. I will learn where my classroom is and where I can take breaks. There will be new routines, and that is okay. If things feel confusing, I can ask a teacher for help. Each day will feel more familiar."
|
| 5 |
+
}
|
mcp_server/templates/haircut.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "getting_a_haircut",
|
| 3 |
+
"title": "Getting a Haircut",
|
| 4 |
+
"story": "Today I am getting a haircut. The hairdresser helps my hair look nice. First, I will sit in a chair. Someone may touch my hair or use scissors. Scissors make small sounds, but they are safe. Hair may fall on my skin and feel tickly, but it can be brushed away. After the haircut, my head will feel lighter and fresh."
|
| 5 |
+
}
|
mcp_server/templates/loud_events.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "loud_and_crowded_events",
|
| 3 |
+
"title": "Loud and Crowded Places",
|
| 4 |
+
"story": "Today I might be in a loud or busy place. There may be many people and sounds. If it feels too loud, I can use headphones or take a break. I can stay close to someone I trust. I do not have to talk if I don’t want to. When the event is over, I can rest somewhere quiet."
|
| 5 |
+
}
|
mcp_server/templates/new_food.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "trying_a_new_food",
|
| 3 |
+
"title": "Trying a New Food",
|
| 4 |
+
"story": "Today I might try a new food. New food has a new smell, texture, or taste. I can look at the food first, smell it, touch it, or lick it. If I don’t like it, that is okay. Trying is already a success."
|
| 5 |
+
}
|
mcp_server/templates/new_place.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "visiting_a_new_place",
|
| 3 |
+
"title": "Visiting a New Place",
|
| 4 |
+
"story": "Today I will go somewhere I have not been before. New places can feel exciting or scary. I can look around slowly and learn what is there. I can stay close to someone I trust. When I’m ready, I can explore more. Every new place becomes familiar after a while."
|
| 5 |
+
}
|
mcp_server/templates/physical_education.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "physical_education_class",
|
| 3 |
+
"title": "PE Class",
|
| 4 |
+
"story": "In PE class, I will move my body and try fun activities. There may be loud sounds like balls bouncing or whistles. I can follow instructions at my own pace. If an activity feels too fast or confusing, I can ask for a pause. PE is for having fun and learning new skills."
|
| 5 |
+
}
|
mcp_server/templates/pickup_from_school.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "getting_picked_up",
|
| 3 |
+
"title": "Getting Picked Up From School",
|
| 4 |
+
"story": "When school is over, someone comes to pick me up. If I am playing, stopping can feel hard. I will get a reminder before it’s time to go. I can take one last turn or finish my activity. When I’m ready, I will leave calmly and go home."
|
| 5 |
+
}
|
mcp_server/templates/social_situations.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "social_situations",
|
| 3 |
+
"title": "Social Situations",
|
| 4 |
+
"story": "Sometimes I will be around people and talk to them. I don’t have to talk a lot. I can use short words or gestures. If I need space, I can step away for a moment. Everyone communicates in different ways, and that is okay. Being myself is enough."
|
| 5 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "comfortool"
|
| 3 |
+
version = "0.0.1"
|
| 4 |
+
requires-python = ">=3.10"
|
| 5 |
+
description = "Comfortool - autism-friendly social stories"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"fastapi>=0.122.0",
|
| 8 |
+
"gradio>=6.0.1",
|
| 9 |
+
"openai>=2.8.1",
|
| 10 |
+
"pillow>=12.0.0",
|
| 11 |
+
"python-dotenv>=1.2.1",
|
| 12 |
+
"requests>=2.32.5",
|
| 13 |
+
"uvicorn>=0.38.0",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
[tool.uv]
|
| 17 |
+
# uv config can go here if needed (optional)
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openai
|
| 2 |
+
gradio
|
| 3 |
+
python-dotenv
|
| 4 |
+
requests
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|