aeonshift commited on
Commit
5e33bad
·
verified ·
1 Parent(s): 9af0b52

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -160
app.py CHANGED
@@ -1,162 +1,103 @@
1
- import faicons as fa
2
- import plotly.express as px
3
-
4
- # Load data and compute static values
5
- from shared import app_dir, tips
6
- from shinywidgets import render_plotly
7
-
8
- from shiny import reactive, render
9
- from shiny.express import input, ui
10
-
11
- bill_rng = (min(tips.total_bill), max(tips.total_bill))
12
-
13
- # Add page title and sidebar
14
- ui.page_opts(title="Restaurant tipping", fillable=True)
15
-
16
- with ui.sidebar(open="desktop"):
17
- ui.input_slider(
18
- "total_bill",
19
- "Bill amount",
20
- min=bill_rng[0],
21
- max=bill_rng[1],
22
- value=bill_rng,
23
- pre="$",
24
- )
25
- ui.input_checkbox_group(
26
- "time",
27
- "Food service",
28
- ["Lunch", "Dinner"],
29
- selected=["Lunch", "Dinner"],
30
- inline=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  )
32
- ui.input_action_button("reset", "Reset filter")
33
-
34
- # Add main content
35
- ICONS = {
36
- "user": fa.icon_svg("user", "regular"),
37
- "wallet": fa.icon_svg("wallet"),
38
- "currency-dollar": fa.icon_svg("dollar-sign"),
39
- "ellipsis": fa.icon_svg("ellipsis"),
40
- }
41
-
42
- with ui.layout_columns(fill=False):
43
- with ui.value_box(showcase=ICONS["user"]):
44
- "Total tippers"
45
-
46
- @render.express
47
- def total_tippers():
48
- tips_data().shape[0]
49
-
50
- with ui.value_box(showcase=ICONS["wallet"]):
51
- "Average tip"
52
-
53
- @render.express
54
- def average_tip():
55
- d = tips_data()
56
- if d.shape[0] > 0:
57
- perc = d.tip / d.total_bill
58
- f"{perc.mean():.1%}"
59
-
60
- with ui.value_box(showcase=ICONS["currency-dollar"]):
61
- "Average bill"
62
-
63
- @render.express
64
- def average_bill():
65
- d = tips_data()
66
- if d.shape[0] > 0:
67
- bill = d.total_bill.mean()
68
- f"${bill:.2f}"
69
-
70
-
71
- with ui.layout_columns(col_widths=[6, 6, 12]):
72
- with ui.card(full_screen=True):
73
- ui.card_header("Tips data")
74
-
75
- @render.data_frame
76
- def table():
77
- return render.DataGrid(tips_data())
78
-
79
- with ui.card(full_screen=True):
80
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
81
- "Total bill vs tip"
82
- with ui.popover(title="Add a color variable", placement="top"):
83
- ICONS["ellipsis"]
84
- ui.input_radio_buttons(
85
- "scatter_color",
86
- None,
87
- ["none", "sex", "smoker", "day", "time"],
88
- inline=True,
89
- )
90
-
91
- @render_plotly
92
- def scatterplot():
93
- color = input.scatter_color()
94
- return px.scatter(
95
- tips_data(),
96
- x="total_bill",
97
- y="tip",
98
- color=None if color == "none" else color,
99
- trendline="lowess",
100
- )
101
-
102
- with ui.card(full_screen=True):
103
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
104
- "Tip percentages"
105
- with ui.popover(title="Add a color variable"):
106
- ICONS["ellipsis"]
107
- ui.input_radio_buttons(
108
- "tip_perc_y",
109
- "Split by:",
110
- ["sex", "smoker", "day", "time"],
111
- selected="day",
112
- inline=True,
113
- )
114
-
115
- @render_plotly
116
- def tip_perc():
117
- from ridgeplot import ridgeplot
118
-
119
- dat = tips_data()
120
- dat["percent"] = dat.tip / dat.total_bill
121
- yvar = input.tip_perc_y()
122
- uvals = dat[yvar].unique()
123
-
124
- samples = [[dat.percent[dat[yvar] == val]] for val in uvals]
125
-
126
- plt = ridgeplot(
127
- samples=samples,
128
- labels=uvals,
129
- bandwidth=0.01,
130
- colorscale="viridis",
131
- colormode="row-index",
132
- )
133
-
134
- plt.update_layout(
135
- legend=dict(
136
- orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
137
- )
138
- )
139
-
140
- return plt
141
-
142
-
143
- ui.include_css(app_dir / "styles.css")
144
-
145
- # --------------------------------------------------------
146
- # Reactive calculations and effects
147
- # --------------------------------------------------------
148
-
149
-
150
- @reactive.calc
151
- def tips_data():
152
- bill = input.total_bill()
153
- idx1 = tips.total_bill.between(bill[0], bill[1])
154
- idx2 = tips.time.isin(input.time())
155
- return tips[idx1 & idx2]
156
-
157
 
158
- @reactive.effect
159
- @reactive.event(input.reset)
160
- def _():
161
- ui.update_slider("total_bill", value=bill_rng)
162
- ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from fastapi import FastAPI
5
+ from mcp.server.lowlevel import Server, NotificationOptions
6
+ from mcp.server.sse import SSEServerTransport
7
+ from mcp import types as mcp_types
8
+ import uvicorn
9
+
10
+ app = FastAPI()
11
+
12
+ # Load environment variables
13
+ AIRTABLE_API_TOKEN = os.getenv("AIRTABLE_API_TOKEN")
14
+ AIRTABLE_BASE_ID = os.getenv("AIRTABLE_BASE_ID")
15
+ TABLE_ID = "tblQECi5f7m4y2NEV"
16
+ AIRTABLE_API_URL = f"https://api.airtable.com/v0/{AIRTABLE_BASE_ID}/{TABLE_ID}"
17
+
18
+ # Helper function for Airtable API requests
19
+ def airtable_request(method, endpoint="", data=None):
20
+ headers = {
21
+ "Authorization": f"Bearer {AIRTABLE_API_TOKEN}",
22
+ "Content-Type": "application/json"
23
+ }
24
+ url = f"{AIRTABLE_API_URL}/{endpoint}" if endpoint else AIRTABLE_API_URL
25
+ response = requests.request(method, url, headers=headers, json=data)
26
+ response.raise_for_status()
27
+ return response.json()
28
+
29
+ # Tool to list records
30
+ async def list_records_tool(request: mcp_types.CallToolRequest):
31
+ try:
32
+ records = airtable_request("GET")
33
+ return {
34
+ "success": True,
35
+ "result": json.dumps(records)
36
+ }
37
+ except Exception as e:
38
+ return {
39
+ "success": False,
40
+ "error": str(e)
41
+ }
42
+
43
+ # Tool to create a record
44
+ async def create_record_tool(request: mcp_types.CallToolRequest):
45
+ try:
46
+ record_data = request.input.get("record_data", {})
47
+ data = {"records": [{"fields": record_data}]}
48
+ response = airtable_request("POST", data=data)
49
+ return {
50
+ "success": True,
51
+ "result": json.dumps(response)
52
+ }
53
+ except Exception as e:
54
+ return {
55
+ "success": False,
56
+ "error": str(e)
57
+ }
58
+
59
+ # MCP Server setup
60
+ async def start_mcp_server():
61
+ tools = [
62
+ mcp_types.ToolDefinition(
63
+ name="list_airtable_records",
64
+ description="Lists all records in the specified Airtable table",
65
+ input_schema={},
66
+ output_schema={"type": "string"}
67
+ ),
68
+ mcp_types.ToolDefinition(
69
+ name="create_airtable_record",
70
+ description="Creates a new record in the specified Airtable table",
71
+ input_schema={"record_data": {"type": "object"}},
72
+ output_schema={"type": "string"}
73
+ )
74
+ ]
75
+
76
+ server = Server(
77
+ tools=tools,
78
+ tool_handlers={
79
+ "list_airtable_records": list_records_tool,
80
+ "create_airtable_record": create_record_tool
81
+ },
82
+ notification_options=NotificationOptions()
83
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ # Hugging Face Spaces handles the port and host, so we specify only the path
86
+ transport = SSEServerTransport(path="/airtable/mcp")
87
+ await server.run(transport)
88
+
89
+ # Start the MCP server when the FastAPI app starts
90
+ @app.on_event("startup")
91
+ async def startup_event():
92
+ import asyncio
93
+ asyncio.create_task(start_mcp_server())
94
+
95
+ # Health check endpoint
96
+ @app.get("/health")
97
+ async def health_check():
98
+ return {"status": "healthy"}
99
+
100
+ if __name__ == "__main__":
101
+ # Hugging Face Spaces uses PORT environment variable
102
+ port = int(os.getenv("PORT", 7860)) # Default to 7860 if PORT is not set
103
+ uvicorn.run(app, host="0.0.0.0", port=port)