ernani commited on
Commit
bc80a58
·
1 Parent(s): 01da5c0

updating - making the first version of fitness app

Browse files
Files changed (2) hide show
  1. app.py +118 -244
  2. tools.py +86 -7
app.py CHANGED
@@ -1,254 +1,129 @@
1
  import gradio as gr
2
  import os
3
- import time
4
- from tools import create_workout_table_graph
5
 
6
- class WorkoutTableGUI:
7
- def __init__(self, graph=None, share=False):
8
- self.graph = graph if graph else create_workout_table_graph()
9
  self.share = share
10
- self.partial_message = ""
11
- self.response = {}
12
- self.max_iterations = 10
13
- self.iterations = []
14
- self.threads = []
15
- self.thread_id = -1
16
- self.thread = {"configurable": {"thread_id": str(self.thread_id)}}
17
  self.demo = self.create_interface()
18
 
19
- def run_agent(self, start, topic, stop_after):
20
- if start:
21
- self.iterations.append(0)
22
- config = {'task': topic, "max_revisions": 2, "revision_number": 0,
23
- 'lnode': "", 'planner': "no plan", 'draft': "no draft", 'feedback': "no feedback",
24
- 'content': ["no content",], 'queries': "no queries", 'count': 0}
25
- self.thread_id += 1 # new agent, new thread
26
- self.threads.append(self.thread_id)
27
- else:
28
- config = None
29
- self.thread = {"configurable": {"thread_id": str(self.thread_id)}}
30
- while self.iterations[self.thread_id] < self.max_iterations:
31
- self.response = self.graph.invoke(config, self.thread)
32
- self.iterations[self.thread_id] += 1
33
- self.partial_message += str(self.response)
34
- self.partial_message += f"\n------------------\n\n"
35
- lnode, nnode, _, rev, acount = self.get_disp_state()
36
- yield self.partial_message, lnode, nnode, self.thread_id, rev, acount
37
- config = None
38
- if not nnode:
39
- return
40
- if lnode in stop_after:
41
- return
42
- return
43
-
44
- def get_disp_state(self):
45
- current_state = self.graph.get_state(self.thread)
46
- lnode = current_state.values["lnode"]
47
- acount = current_state.values["count"]
48
- rev = current_state.values["revision_number"]
49
- nnode = current_state.next
50
- return lnode, nnode, self.thread_id, rev, acount
51
-
52
- def get_state(self, key):
53
- current_values = self.graph.get_state(self.thread)
54
- if key in current_values.values:
55
- lnode, nnode, self.thread_id, rev, astep = self.get_disp_state()
56
- new_label = f"last_node: {lnode}, thread_id: {self.thread_id}, rev: {rev}, step: {astep}"
57
- return gr.update(label=new_label, value=current_values.values[key])
58
- else:
59
- return ""
60
-
61
- def get_content(self):
62
- current_values = self.graph.get_state(self.thread)
63
- if "content" in current_values.values:
64
- content = current_values.values["content"]
65
- lnode, nnode, thread_id, rev, astep = self.get_disp_state()
66
- new_label = f"last_node: {lnode}, thread_id: {self.thread_id}, rev: {rev}, step: {astep}"
67
- return gr.update(label=new_label, value="\n\n".join(item for item in content) + "\n\n")
68
- else:
69
- return ""
70
-
71
- def update_hist_pd(self):
72
- hist = []
73
- for state in self.graph.get_state_history(self.thread):
74
- if state.metadata['step'] < 1:
75
- continue
76
- # Use a default value if thread_ts is not present
77
- thread_ts = state.config['configurable'].get('thread_ts', 'N/A')
78
- tid = state.config['configurable']['thread_id']
79
- count = state.values['count']
80
- lnode = state.values['lnode']
81
- rev = state.values['revision_number']
82
- nnode = state.next
83
- st = f"{tid}:{count}:{lnode}:{nnode}:{rev}:{thread_ts}"
84
- hist.append(st)
85
- return gr.Dropdown(label="update_state from: thread:count:last_node:next_node:rev:thread_ts",
86
- choices=hist, value=hist[0] if hist else None, interactive=True)
87
-
88
- def find_config(self, thread_ts):
89
- for state in self.graph.get_state_history(self.thread):
90
- config = state.config
91
- # Skip if thread_ts is not present or doesn't match
92
- if 'thread_ts' not in config['configurable'] or config['configurable']['thread_ts'] != thread_ts:
93
- continue
94
- return config
95
- return None
96
-
97
- def copy_state(self, hist_str):
98
- thread_ts = hist_str.split(":")[-1]
99
- config = self.find_config(thread_ts)
100
- if config is None:
101
- return None, None, None, None, None
102
- state = self.graph.get_state(config)
103
- self.graph.update_state(self.thread, state.values, as_node=state.values['lnode'])
104
- new_state = self.graph.get_state(self.thread)
105
- new_thread_ts = new_state.config['configurable'].get('thread_ts', 'N/A')
106
- tid = new_state.config['configurable']['thread_id']
107
- count = new_state.values['count']
108
- lnode = new_state.values['lnode']
109
- rev = new_state.values['revision_number']
110
- nnode = new_state.next
111
- return lnode, nnode, new_thread_ts, rev, count
112
-
113
- def update_thread_pd(self):
114
- return gr.Dropdown(label="choose thread", choices=self.threads, value=self.thread_id, interactive=True)
115
-
116
- def switch_thread(self, new_thread_id):
117
- self.thread = {"configurable": {"thread_id": str(new_thread_id)}}
118
- self.thread_id = new_thread_id
119
- return
120
-
121
- def modify_state(self, key, asnode, new_state):
122
- current_values = self.graph.get_state(self.thread)
123
- current_values.values[key] = new_state
124
- self.graph.update_state(self.thread, current_values.values, as_node=asnode)
125
- return
126
 
127
- def create_interface(self):
128
- with gr.Blocks(theme=gr.themes.Default(spacing_size='sm', text_size="sm")) as demo:
129
-
130
- def updt_disp():
131
- current_state = self.graph.get_state(self.thread)
132
- hist = []
133
- for state in self.graph.get_state_history(self.thread):
134
- if state.metadata['step'] < 1:
135
- continue
136
- # Use a default value if thread_ts is not present
137
- s_thread_ts = state.config['configurable'].get('thread_ts', 'N/A')
138
- s_tid = state.config['configurable']['thread_id']
139
- s_count = state.values['count']
140
- s_lnode = state.values['lnode']
141
- s_rev = state.values['revision_number']
142
- s_nnode = state.next
143
- st = f"{s_tid}:{s_count}:{s_lnode}:{s_nnode}:{s_rev}:{s_thread_ts}"
144
- hist.append(st)
145
- if not current_state.metadata:
146
- return {}
147
- else:
148
- return {
149
- topic_bx: current_state.values["task"],
150
- lnode_bx: current_state.values["lnode"],
151
- count_bx: current_state.values["count"],
152
- revision_bx: current_state.values["revision_number"],
153
- nnode_bx: current_state.next,
154
- threadid_bx: self.thread_id,
155
- thread_pd: gr.Dropdown(label="choose thread", choices=self.threads, value=self.thread_id, interactive=True),
156
- step_pd: gr.Dropdown(label="update_state from: thread:count:last_node:next_node:rev:thread_ts",
157
- choices=hist, value=hist[0] if hist else None, interactive=True),
158
- }
159
-
160
- def get_snapshots():
161
- new_label = f"thread_id: {self.thread_id}, Summary of snapshots"
162
- sstate = ""
163
- for state in self.graph.get_state_history(self.thread):
164
- for key in ['plan', 'draft', 'feedback']:
165
- if key in state.values:
166
- state.values[key] = state.values[key][:80] + "..."
167
- if 'content' in state.values:
168
- for i in range(len(state.values['content'])):
169
- state.values['content'][i] = state.values['content'][i][:20] + '...'
170
- if 'writes' in state.metadata:
171
- state.metadata['writes'] = "not shown"
172
- sstate += str(state) + "\n\n"
173
- return gr.update(label=new_label, value=sstate)
174
 
175
- def vary_btn(stat):
176
- return gr.update(variant=stat)
 
177
 
178
- with gr.Tab("Agent"):
179
- with gr.Row():
180
- topic_bx = gr.Textbox(label="Workout Table", value="Workout Table for a 30 year old male who wants to gain muscle mass and strength")
181
- gen_btn = gr.Button("Generate Workout Table", scale=0, min_width=80, variant='primary')
182
- cont_btn = gr.Button("Continue Workout Table", scale=0, min_width=80)
183
- with gr.Row():
184
- lnode_bx = gr.Textbox(label="last node", min_width=100)
185
- nnode_bx = gr.Textbox(label="next node", min_width=100)
186
- threadid_bx = gr.Textbox(label="Thread", scale=0, min_width=80)
187
- revision_bx = gr.Textbox(label="Draft Rev", scale=0, min_width=80)
188
- count_bx = gr.Textbox(label="count", scale=0, min_width=80)
189
- with gr.Accordion("Manage Agent", open=False):
190
- checks = list(self.graph.nodes.keys())
191
- checks.remove('__start__')
192
- stop_after = gr.CheckboxGroup(checks, label="Interrupt After State", value=checks, scale=0, min_width=400)
193
  with gr.Row():
194
- thread_pd = gr.Dropdown(choices=self.threads, interactive=True, label="select thread", min_width=120, scale=0)
195
- step_pd = gr.Dropdown(choices=['N/A'], interactive=True, label="select step", min_width=160, scale=1)
196
- live = gr.Textbox(label="Live Agent Output", lines=5, max_lines=5)
197
-
198
- # actions
199
- sdisps = [topic_bx, lnode_bx, nnode_bx, threadid_bx, revision_bx, count_bx, step_pd, thread_pd]
200
- thread_pd.input(self.switch_thread, [thread_pd], None).then(
201
- fn=updt_disp, inputs=None, outputs=sdisps)
202
- step_pd.input(self.copy_state, [step_pd], None).then(
203
- fn=updt_disp, inputs=None, outputs=sdisps)
204
- gen_btn.click(vary_btn, gr.Number("secondary", visible=False), gen_btn).then(
205
- fn=self.run_agent, inputs=[gr.Number(True, visible=False), topic_bx, stop_after], outputs=[live], show_progress=True).then(
206
- fn=updt_disp, inputs=None, outputs=sdisps).then(
207
- vary_btn, gr.Number("primary", visible=False), gen_btn).then(
208
- vary_btn, gr.Number("primary", visible=False), cont_btn)
209
- cont_btn.click(vary_btn, gr.Number("secondary", visible=False), cont_btn).then(
210
- fn=self.run_agent, inputs=[gr.Number(False, visible=False), topic_bx, stop_after],
211
- outputs=[live]).then(
212
- fn=updt_disp, inputs=None, outputs=sdisps).then(
213
- vary_btn, gr.Number("primary", visible=False), cont_btn)
214
-
215
- with gr.Tab("Workout Table"):
216
- with gr.Row():
217
- refresh_btn = gr.Button("Refresh")
218
- modify_btn = gr.Button("Modify")
219
- plan = gr.Textbox(label="Plan", lines=10, interactive=True)
220
- refresh_btn.click(fn=self.get_state, inputs=gr.Number("plan", visible=False), outputs=plan)
221
- modify_btn.click(fn=self.modify_state, inputs=[gr.Number("plan", visible=False),
222
- gr.Number("planner", visible=False), plan], outputs=None).then(
223
- fn=updt_disp, inputs=None, outputs=sdisps)
224
- with gr.Tab("Research Content"):
225
- refresh_btn = gr.Button("Refresh")
226
- content_bx = gr.Textbox(label="content", lines=10)
227
- refresh_btn.click(fn=self.get_content, inputs=None, outputs=content_bx)
228
- with gr.Tab("Draft"):
229
- with gr.Row():
230
- refresh_btn = gr.Button("Refresh")
231
- modify_btn = gr.Button("Modify")
232
- draft_bx = gr.Textbox(label="draft", lines=10, interactive=True)
233
- refresh_btn.click(fn=self.get_state, inputs=gr.Number("draft", visible=False), outputs=draft_bx)
234
- modify_btn.click(fn=self.modify_state, inputs=[gr.Number("draft", visible=False),
235
- gr.Number("generate", visible=False), draft_bx], outputs=None).then(
236
- fn=updt_disp, inputs=None, outputs=sdisps)
237
- with gr.Tab("Feedback"):
238
- with gr.Row():
239
- refresh_btn = gr.Button("Refresh")
240
- modify_btn = gr.Button("Modify")
241
- feedback_bx = gr.Textbox(label="Feedback", lines=10, interactive=True)
242
- refresh_btn.click(fn=self.get_state, inputs=gr.Number("feedback", visible=False), outputs=feedback_bx)
243
- modify_btn.click(fn=self.modify_state, inputs=[gr.Number("feedback", visible=False),
244
- gr.Number("reflect", visible=False),
245
- feedback_bx], outputs=None).then(
246
- fn=updt_disp, inputs=None, outputs=sdisps)
247
- with gr.Tab("StateSnapShots"):
248
- with gr.Row():
249
- refresh_btn = gr.Button("Refresh")
250
- snapshots = gr.Textbox(label="State Snapshots Summaries")
251
- refresh_btn.click(fn=get_snapshots, inputs=None, outputs=snapshots)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  return demo
253
 
254
  def launch(self, share=None):
@@ -257,7 +132,6 @@ class WorkoutTableGUI:
257
  else:
258
  self.demo.launch(share=self.share)
259
 
260
-
261
  if __name__ == "__main__":
262
- workout_table = WorkoutTableGUI()
263
- workout_table.launch()
 
1
  import gradio as gr
2
  import os
3
+ from tools import generate_fitness_plan
 
4
 
5
+ class FitnessProfileGUI:
6
+ def __init__(self, share=False):
 
7
  self.share = share
 
 
 
 
 
 
 
8
  self.demo = self.create_interface()
9
 
10
+ def create_fitness_plan(self, age, weight, height, gender, primary_goal, target_timeframe,
11
+ workout_preferences, workout_duration, workout_days,
12
+ activity_level, health_conditions, dietary_preferences):
13
+ # Generate the fitness plan using the tools module
14
+ result = generate_fitness_plan(
15
+ age=age,
16
+ weight=weight,
17
+ height=height,
18
+ gender=gender,
19
+ primary_goal=primary_goal,
20
+ target_timeframe=target_timeframe,
21
+ workout_preferences=workout_preferences,
22
+ workout_duration=workout_duration,
23
+ workout_days=workout_days,
24
+ activity_level=activity_level,
25
+ health_conditions=health_conditions,
26
+ dietary_preferences=dietary_preferences
27
+ )
28
+
29
+ # Format the output
30
+ output = f"""
31
+ {result['profile_summary']}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ FITNESS PLAN
34
+ ===========
35
+ {result['fitness_plan']}
36
+ """
37
+ return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ def create_interface(self):
40
+ with gr.Blocks(theme=gr.themes.Default()) as demo:
41
+ gr.Markdown("# AI Fitness Coach")
42
 
43
+ with gr.Tabs():
44
+ with gr.Tab("Create Fitness Plan"):
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  with gr.Row():
46
+ age = gr.Number(label="Age", value=0)
47
+ weight = gr.Number(label="Weight (kg)", value=0)
48
+ height = gr.Number(label="Height (cm)", value=0)
49
+ gender = gr.Radio(choices=["Male", "Female"], label="Gender")
50
+
51
+ primary_goal = gr.Dropdown(
52
+ choices=[
53
+ "Weight Loss",
54
+ "Muscle Gain",
55
+ "Strength Building",
56
+ "General Fitness",
57
+ "Endurance Improvement",
58
+ "Flexibility Enhancement"
59
+ ],
60
+ label="Primary Goal"
61
+ )
62
+
63
+ target_timeframe = gr.Dropdown(
64
+ choices=[
65
+ "1 month",
66
+ "3 months",
67
+ "6 months",
68
+ "1 year",
69
+ "Ongoing"
70
+ ],
71
+ label="Target Timeframe"
72
+ )
73
+
74
+ workout_preferences = gr.CheckboxGroup(
75
+ choices=["Cardio", "Strength training", "Yoga", "Pilates", "Flexibility exercises", "HIIT"],
76
+ label="Workout Type Preferences"
77
+ )
78
+
79
+ workout_duration = gr.Slider(
80
+ minimum=15,
81
+ maximum=120,
82
+ step=15,
83
+ label="Preferred Workout Duration (minutes)",
84
+ value=15
85
+ )
86
+
87
+ workout_days = gr.CheckboxGroup(
88
+ choices=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
89
+ label="Preferred Workout Days"
90
+ )
91
+
92
+ activity_level = gr.Radio(
93
+ choices=["Sedentary", "Lightly active", "Moderately active", "Highly active"],
94
+ label="Current Activity Level"
95
+ )
96
+
97
+ health_conditions = gr.Textbox(
98
+ label="Health Conditions or Injuries",
99
+ placeholder="List any health conditions, injuries, or limitations...",
100
+ lines=3
101
+ )
102
+
103
+ dietary_preferences = gr.Textbox(
104
+ label="Dietary Preferences (Optional)",
105
+ placeholder="List any dietary preferences or restrictions...",
106
+ lines=3
107
+ )
108
+
109
+ submit_btn = gr.Button("Generate Fitness Plan", variant="primary")
110
+ output = gr.Textbox(label="Generated Fitness Plan", lines=10)
111
+
112
+ submit_btn.click(
113
+ fn=self.create_fitness_plan,
114
+ inputs=[
115
+ age, weight, height, gender,
116
+ primary_goal, target_timeframe,
117
+ workout_preferences, workout_duration,
118
+ workout_days, activity_level,
119
+ health_conditions, dietary_preferences
120
+ ],
121
+ outputs=output
122
+ )
123
+
124
+ with gr.Tab("Update Fitness Plan"):
125
+ gr.Markdown("Coming soon: Update your existing fitness plan")
126
+
127
  return demo
128
 
129
  def launch(self, share=None):
 
132
  else:
133
  self.demo.launch(share=self.share)
134
 
 
135
  if __name__ == "__main__":
136
+ fitness_profile = FitnessProfileGUI()
137
+ fitness_profile.launch()
tools.py CHANGED
@@ -1,5 +1,5 @@
1
  from langgraph.graph import StateGraph, END
2
- from typing import TypedDict, Annotated, List
3
  import operator
4
  from langgraph.checkpoint.sqlite import SqliteSaver
5
  from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage, ChatMessage
@@ -28,12 +28,6 @@ class Queries(BaseModel):
28
  # Tool functions
29
  def plan_node(model, state: AgentState):
30
  table_output = """
31
- Workout Table Sequence:
32
- - Section 1: Warm-up
33
- - Section 2: Strength Training
34
- - Section 3: Cardio
35
- - Section 4: Cool-down
36
-
37
  Workout Table Example:
38
  Workout Table Full Body (3 times per week):
39
  Day 1:
@@ -194,3 +188,88 @@ def create_workout_table_graph():
194
 
195
  return graph
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from langgraph.graph import StateGraph, END
2
+ from typing import TypedDict, Annotated, List, Dict
3
  import operator
4
  from langgraph.checkpoint.sqlite import SqliteSaver
5
  from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage, ChatMessage
 
28
  # Tool functions
29
  def plan_node(model, state: AgentState):
30
  table_output = """
 
 
 
 
 
 
31
  Workout Table Example:
32
  Workout Table Full Body (3 times per week):
33
  Day 1:
 
188
 
189
  return graph
190
 
191
+ def calculate_bmi(weight: float, height: float) -> float:
192
+ """Calculate BMI from weight in kg and height in cm."""
193
+ height_in_meters = height / 100
194
+ return weight / (height_in_meters * height_in_meters)
195
+
196
+ def get_bmi_category(bmi: float) -> str:
197
+ """Get BMI category from BMI value."""
198
+ if bmi < 18.5:
199
+ return "Underweight"
200
+ elif bmi < 25:
201
+ return "Normal weight"
202
+ elif bmi < 30:
203
+ return "Overweight"
204
+ else:
205
+ return "Obese"
206
+
207
+ def generate_fitness_plan(
208
+ age: int,
209
+ weight: float,
210
+ height: float,
211
+ gender: str,
212
+ primary_goal: str,
213
+ target_timeframe: str,
214
+ workout_preferences: List[str],
215
+ workout_duration: int,
216
+ workout_days: List[str],
217
+ activity_level: str,
218
+ health_conditions: str,
219
+ dietary_preferences: str
220
+ ) -> Dict:
221
+ """Generate a personalized fitness plan based on user inputs."""
222
+
223
+ # Calculate BMI and get category
224
+ bmi = calculate_bmi(weight, height)
225
+ bmi_category = get_bmi_category(bmi)
226
+
227
+ # Create a profile summary
228
+ profile_summary = f"""
229
+ User Profile:
230
+ - Age: {age} years
231
+ - Gender: {gender}
232
+ - BMI: {bmi:.1f} ({bmi_category})
233
+ - Activity Level: {activity_level}
234
+ - Primary Goal: {primary_goal}
235
+ - Target Timeframe: {target_timeframe}
236
+ - Preferred Workout Types: {', '.join(workout_preferences)}
237
+ - Workout Duration: {workout_duration} minutes
238
+ - Workout Days: {', '.join(workout_days)}
239
+ - Health Conditions: {health_conditions if health_conditions else 'None reported'}
240
+ - Dietary Preferences: {dietary_preferences if dietary_preferences else 'None reported'}
241
+ """
242
+
243
+ # Initialize the AI model
244
+ model = ChatOpenAI(
245
+ model="gpt-3.5-turbo",
246
+ temperature=0.7
247
+ )
248
+
249
+ # Create the prompt for generating the fitness plan
250
+ system_prompt = """You are an expert fitness trainer and nutritionist. Your task is to create a detailed,
251
+ personalized fitness plan based on the user's profile. The plan should include:
252
+
253
+ 1. Weekly workout schedule
254
+ 2. Specific exercises for each day
255
+ 3. Exercise intensity and progression plan
256
+ 4. Nutritional recommendations
257
+ 5. Safety considerations and modifications
258
+ 6. Progress tracking metrics
259
+
260
+ Consider the user's current fitness level, goals, preferences, and any health conditions when creating the plan.
261
+ Make the plan realistic and achievable within their target timeframe."""
262
+
263
+ # Generate the fitness plan
264
+ messages = [
265
+ SystemMessage(content=system_prompt),
266
+ HumanMessage(content=f"Please create a fitness plan for the following user profile:\n{profile_summary}")
267
+ ]
268
+
269
+ response = model.invoke(messages)
270
+
271
+ return {
272
+ "profile_summary": profile_summary,
273
+ "fitness_plan": response.content
274
+ }
275
+