brainsqueeze commited on
Commit
291dbf7
·
verified ·
1 Parent(s): 0a69585

Add feedback tab

Browse files
Files changed (1) hide show
  1. app.py +160 -11
app.py CHANGED
@@ -1,12 +1,66 @@
 
1
  import os
2
 
3
  import gradio as gr
4
 
5
- from common import org_search_component as oss
6
- from formatting import process_reasons, parse_pcs_descriptions, parse_geo_descriptions
7
- from services import RfpRecommend
 
 
 
 
 
8
 
9
  api = RfpRecommend()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
 
12
  def recommend_invoke(recipient: gr.State):
@@ -27,14 +81,14 @@ def recommend_invoke(recipient: gr.State):
27
  parse_pcs_descriptions(rfp["taxonomy"]),
28
  parse_geo_descriptions(rfp["area_served"])
29
  ])
30
- return (
31
- output,
32
- process_reasons(response.get("meta", {}) or {}),
33
- response.get("recommendations", [])
34
- )
35
 
 
 
36
 
37
- def build_demo():
 
 
 
38
  with gr.Blocks(theme=gr.themes.Soft(), title="RFP recommendations") as demo:
39
  gr.Markdown(
40
  """
@@ -82,8 +136,7 @@ def build_demo():
82
  interactive=False
83
  )
84
 
85
- with gr.Accordion("JSON output", open=False):
86
- recommendations_json = gr.JSON(label="Recommended RFPs JSON")
87
 
88
  # pylint: disable=no-member
89
  recommend.click(
@@ -92,9 +145,105 @@ def build_demo():
92
  outputs=[rec_outputs, reasons_output, recommendations_json]
93
  )
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  return demo
96
 
97
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if __name__ == '__main__':
99
  app = build_demo()
100
  app.queue(max_size=5).launch(
 
1
+ from typing import List, Literal, Tuple, TypedDict
2
  import os
3
 
4
  import gradio as gr
5
 
6
+ try:
7
+ from common import org_search_component as oss
8
+ from formatting import process_reasons, parse_pcs_descriptions, parse_geo_descriptions
9
+ from services import RfpRecommend, RfpFeedback
10
+ except ImportError:
11
+ from ..common import org_search_component as oss
12
+ from .formatting import process_reasons, parse_pcs_descriptions, parse_geo_descriptions
13
+ from .services import RfpRecommend, RfpFeedback
14
 
15
  api = RfpRecommend()
16
+ reporting = RfpFeedback()
17
+
18
+ class LoggedComponents(TypedDict):
19
+ recommendations: gr.components.Component
20
+ ratings: List[gr.components.Component]
21
+ correctness: gr.components.Component
22
+ sufficiency: gr.components.Component
23
+ comments: gr.components.Component
24
+ email: gr.components.Component
25
+
26
+
27
+ def single_recommendation_response(
28
+ item_number: int,
29
+ rec_type: Literal["RFP"] = "RFP"
30
+ ) -> gr.Radio:
31
+ """Generates a radio button group to provide feedback for single recommendation indexed by `item_number`.
32
+ Since the index values start from `0` we add `1` to indicate the ordinal value in the info text.
33
+
34
+ Parameters
35
+ ----------
36
+ item_number : int
37
+ Recommendation index starting from 0
38
+
39
+ Returns
40
+ -------
41
+ gr.Radio
42
+ """
43
+
44
+ ordinal = str(item_number + 1)
45
+
46
+ suffix = "th"
47
+ if ordinal.endswith('1') and not ordinal.endswith('11'):
48
+ suffix = "st"
49
+ elif ordinal.endswith('2') and not ordinal.endswith('12'):
50
+ suffix = "nd"
51
+ elif ordinal.endswith('3') and not ordinal.endswith('13'):
52
+ suffix = "rd"
53
+
54
+ elem = gr.Radio(
55
+ choices=[
56
+ "Not relevant and not useful",
57
+ "Relevant but not useful",
58
+ "Relevant and useful"
59
+ ],
60
+ label=f"Recommendation #{ordinal}",
61
+ info=f"Evaluate the {ordinal}{suffix} {rec_type} (if applicable)"
62
+ )
63
+ return elem
64
 
65
 
66
  def recommend_invoke(recipient: gr.State):
 
81
  parse_pcs_descriptions(rfp["taxonomy"]),
82
  parse_geo_descriptions(rfp["area_served"])
83
  ])
 
 
 
 
 
84
 
85
+ if len(output) == 0:
86
+ raise gr.Error("No relevant RFPs were found, please try again in the future as new RFPs become available.")
87
 
88
+ return output, process_reasons(response.get("meta", {}) or {}), response
89
+
90
+
91
+ def build_recommender() -> Tuple[LoggedComponents, gr.Blocks]:
92
  with gr.Blocks(theme=gr.themes.Soft(), title="RFP recommendations") as demo:
93
  gr.Markdown(
94
  """
 
136
  interactive=False
137
  )
138
 
139
+ recommendations_json = gr.JSON(label="Recommended RFPs JSON", visible=False)
 
140
 
141
  # pylint: disable=no-member
142
  recommend.click(
 
145
  outputs=[rec_outputs, reasons_output, recommendations_json]
146
  )
147
 
148
+ logged = LoggedComponents(
149
+ recommendations=recommendations_json
150
+ )
151
+
152
+ return logged, demo
153
+
154
+
155
+ def build_feedback(
156
+ components: LoggedComponents,
157
+ N: int = 5,
158
+ rec_type: Literal["RFP"] = "RFP",
159
+ ) -> gr.Blocks:
160
+
161
+ def handle_feedback(*args):
162
+ try:
163
+ reporting(
164
+ recommendation_data=args[0],
165
+ ratings=list(args[1: (N + 1)]),
166
+ info_is_correct=args[N + 1],
167
+ info_is_sufficient=args[N + 2],
168
+ comments=args[N + 3],
169
+ email=args[N + 4]
170
+ )
171
+ gr.Info("Thank you for providing feedback!")
172
+ except Exception as ex:
173
+ if hasattr(ex, "response"):
174
+ error_msg = ex.response.json().get("response", {}).get("error")
175
+ raise gr.Error(f"Failed to submit feedback: {error_msg}")
176
+ raise gr.Error("Failed to submit feedback")
177
+
178
+ feedback_components = []
179
+ with gr.Blocks(theme=gr.themes.Soft(), title="Candid AI demo") as demo:
180
+ gr.Markdown("""
181
+ <h1>Help us improve this tool with your valuable feedback</h1>
182
+
183
+ Please provide feedback for the recommendations on the previous tab.
184
+
185
+ It is not required to provide feedback on all recommendations before submitting.
186
+ """
187
+ )
188
+
189
+ with gr.Row():
190
+ with gr.Column():
191
+ with gr.Group():
192
+ for i in range(N):
193
+ f = single_recommendation_response(i, rec_type=rec_type)
194
+ feedback_components.append(f)
195
+ if "ratings" not in components:
196
+ components["ratings"] = [f]
197
+ else:
198
+ components["ratings"].append(f)
199
+
200
+ correctness = gr.Radio(
201
+ choices=["True", "False"],
202
+ label="Information is correct?",
203
+ info="Are the displayed RFP details correct?"
204
+ )
205
+ sufficiency = gr.Radio(
206
+ choices=["True", "False"],
207
+ label="Sufficient data?",
208
+ info="Is enough RFP data available to provide meaningful recommendations?"
209
+ )
210
+
211
+ comment = gr.Textbox(label="Additional comments (optional)", lines=4)
212
+ email = gr.Textbox(label="Your email (optional)", lines=1)
213
+
214
+ components["correctness"] = correctness
215
+ components["sufficiency"] = sufficiency
216
+ components["comments"] = comment
217
+ components["email"] = email
218
+
219
+ with gr.Row():
220
+ submit = gr.Button("Submit Feedback", variant='primary', scale=5)
221
+ gr.ClearButton(components=feedback_components, variant="stop")
222
+
223
+ # pylint: disable=no-member
224
+ submit.click(
225
+ fn=handle_feedback,
226
+ inputs=[comp for k, cl in components.items() for comp in (cl if isinstance(cl, list) else [cl])],
227
+ outputs=None,
228
+ show_api=False,
229
+ api_name=False,
230
+ preprocess=False,
231
+ )
232
  return demo
233
 
234
 
235
+
236
+ def build_demo():
237
+ logger, recommender = build_recommender()
238
+ feedback = build_feedback(logger)
239
+ return gr.TabbedInterface(
240
+ interface_list=[recommender, feedback],
241
+ tab_names=["RFP recommendations", "Feedback"],
242
+ title="Candid's RFP recommendation engine",
243
+ theme=gr.themes.Soft()
244
+ )
245
+
246
+
247
  if __name__ == '__main__':
248
  app = build_demo()
249
  app.queue(max_size=5).launch(