Skip to content

Commit 485cb6a

Browse files
authored
Add spinner element (#25)
1 parent 29b0922 commit 485cb6a

File tree

3 files changed

+64
-50
lines changed

3 files changed

+64
-50
lines changed

app.py

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@
1717
</svg>
1818
'''
1919

20+
2021
def get_svg_base64(svg_content):
21-
b64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
22-
return f"data:image/svg+xml;base64,{b64}"
22+
b64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
23+
return f"data:image/svg+xml;base64,{b64}"
24+
2325

2426
# Configure the page with your brand colors
2527
st.set_page_config(
26-
page_title="VetsAI: Vets Who Code Assistant",
27-
page_icon=get_svg_base64(svg_content),
28-
layout="wide"
28+
page_title="VetsAI: Vets Who Code Assistant",
29+
page_icon=get_svg_base64(svg_content),
30+
layout="wide"
2931
)
3032

3133
# Define VWC brand colors and add custom CSS
@@ -114,12 +116,13 @@ def get_svg_base64(svg_content):
114116
if not client.api_key: # Changed from openai.api_key to client.api_key
115117
raise ValueError("OpenAI API key not found in Streamlit secrets.")
116118

119+
117120
def parse_mos_file(file_content: str) -> dict:
118121
"""Parse military job code text file content into a structured dictionary."""
119122
lines = file_content.strip().split('\n')
120123
job_code, title, description = "", "", []
121124
parsing_description = False
122-
125+
123126
for line in lines:
124127
line = line.strip()
125128
if not line:
@@ -130,33 +133,35 @@ def parse_mos_file(file_content: str) -> dict:
130133
parsing_description = True
131134
elif parsing_description:
132135
description.append(line)
133-
136+
134137
for line in description:
135138
if line:
136139
title = line
137140
break
138-
141+
139142
full_text = ' '.join(description).lower()
140143
category = "general"
141144
category_keywords = {
142-
"information_technology": ["technology", "computer", "network", "data", "software", "hardware", "system", "database"],
145+
"information_technology": ["technology", "computer", "network", "data", "software", "hardware", "system",
146+
"database"],
143147
"communications": ["communications", "signal", "radio", "transmission", "telecom"],
144148
"intelligence": ["intelligence", "analysis", "surveillance", "reconnaissance"],
145149
"maintenance": ["maintenance", "repair", "technical", "equipment"],
146150
"cyber": ["cyber", "security", "information assurance", "cryptographic"]
147151
}
148-
152+
149153
for cat, keywords in category_keywords.items():
150154
if any(keyword in full_text for keyword in keywords):
151155
category = cat
152156
break
153-
157+
154158
return {
155159
"title": title or "Military Professional",
156160
"category": category,
157161
"skills": [line for line in description if line and len(line) > 10]
158162
}
159163

164+
160165
def load_military_job_codes() -> dict:
161166
base_path = "data/employment_transitions/job_codes"
162167
job_codes = {}
@@ -167,7 +172,7 @@ def load_military_job_codes() -> dict:
167172
"navy": {"path": "navy", "prefix": "RATE"},
168173
"marine_corps": {"path": "marine_corps", "prefix": "MOS"}
169174
}
170-
175+
171176
for branch, info in branches.items():
172177
branch_path = os.path.join(base_path, info["path"])
173178
if os.path.exists(branch_path):
@@ -191,6 +196,7 @@ def load_military_job_codes() -> dict:
191196
continue
192197
return job_codes
193198

199+
194200
def map_to_vwc_path(category: str, skills: List[str]) -> dict:
195201
"""Map military job categories and skills to VWC tech stack paths."""
196202
default_path = {
@@ -201,7 +207,7 @@ def map_to_vwc_path(category: str, skills: List[str]) -> dict:
201207
"Python with FastAPI/Django for backend"
202208
]
203209
}
204-
210+
205211
tech_paths = {
206212
"information_technology": {
207213
"path": "Full Stack Development",
@@ -244,36 +250,37 @@ def map_to_vwc_path(category: str, skills: List[str]) -> dict:
244250
]
245251
}
246252
}
247-
253+
248254
skill_keywords = {
249255
"programming": "software",
250256
"database": "data",
251257
"network": "communications",
252258
"security": "cyber",
253259
"analysis": "intelligence"
254260
}
255-
261+
256262
if category.lower() in tech_paths:
257263
return tech_paths[category.lower()]
258-
264+
259265
for skill in skills:
260266
skill_lower = skill.lower()
261267
for keyword, category in skill_keywords.items():
262268
if keyword in skill_lower and category in tech_paths:
263269
return tech_paths[category]
264-
270+
265271
return default_path
266272

273+
267274
def translate_military_code(code: str, job_codes: dict) -> dict:
268275
"""Translate military code to VWC development path."""
269276
code = code.upper().strip()
270277
prefixes = ["MOS", "AFSC", "RATE"]
271278
for prefix in prefixes:
272279
if code.startswith(prefix):
273280
code = code.replace(prefix, "").strip()
274-
281+
275282
possible_codes = [f"MOS_{code}", f"AFSC_{code}", f"RATE_{code}"]
276-
283+
277284
for possible_code in possible_codes:
278285
if possible_code in job_codes:
279286
job_data = job_codes[possible_code]
@@ -287,7 +294,7 @@ def translate_military_code(code: str, job_codes: dict) -> dict:
287294
"skills": job_data.get('skills', [])
288295
}
289296
}
290-
297+
291298
return {
292299
"found": False,
293300
"data": {
@@ -307,6 +314,7 @@ def translate_military_code(code: str, job_codes: dict) -> dict:
307314
}
308315
}
309316

317+
310318
def get_chat_response(messages: List[Dict]) -> str:
311319
"""Get response from OpenAI chat completion."""
312320
try:
@@ -320,6 +328,7 @@ def get_chat_response(messages: List[Dict]) -> str:
320328
logger.error(f"OpenAI API error: {e}")
321329
raise
322330

331+
323332
def export_chat_history(chat_history: List[Dict]) -> str:
324333
"""Export chat history to JSON."""
325334
export_data = {
@@ -328,6 +337,7 @@ def export_chat_history(chat_history: List[Dict]) -> str:
328337
}
329338
return json.dumps(export_data, indent=2)
330339

340+
331341
def save_feedback(feedback: Dict):
332342
"""Save user feedback to file."""
333343
feedback_dir = "feedback"
@@ -339,37 +349,39 @@ def save_feedback(feedback: Dict):
339349
with open(feedback_file, 'w') as f:
340350
json.dump(feedback, f, indent=2)
341351

352+
342353
def handle_command(command: str) -> str:
343354
"""Handle special commands including MOS translation."""
344355
parts = command.lower().split()
345356
if not parts:
346357
return None
347-
358+
348359
cmd = parts[0]
349360
if cmd in ['/mos', '/afsc', '/rate']:
350361
if len(parts) < 2:
351362
return "Please provide a military job code. Example: `/mos 25B`"
352-
363+
353364
code = parts[1]
354365
translation = translate_military_code(code, st.session_state.job_codes)
355366
if translation['found']:
356367
return (
357-
f"🎖️ **{translation['data']['title']}** ({translation['data']['branch']})\n\n"
358-
f"💻 **VWC Development Path**: {translation['data']['dev_path']}\n\n"
359-
"🔧 **Military Skills**:\n" +
360-
"\n".join(f"- {skill}" for skill in translation['data']['skills']) +
361-
"\n\n📚 **VWC Tech Focus**:\n" +
362-
"\n".join(f"{i+1}. {focus}" for i, focus in enumerate(translation['data']['tech_focus']))
368+
f"🎖️ **{translation['data']['title']}** ({translation['data']['branch']})\n\n"
369+
f"💻 **VWC Development Path**: {translation['data']['dev_path']}\n\n"
370+
"🔧 **Military Skills**:\n" +
371+
"\n".join(f"- {skill}" for skill in translation['data']['skills']) +
372+
"\n\n📚 **VWC Tech Focus**:\n" +
373+
"\n".join(f"{i + 1}. {focus}" for i, focus in enumerate(translation['data']['tech_focus']))
363374
)
364375
else:
365376
return (
366-
"I don't have that specific code in my database, but here's a recommended "
367-
"VWC learning path based on general military experience:\n\n" +
368-
"\n".join(f"{i+1}. {focus}" for i, focus in enumerate(translation['data']['tech_focus']))
377+
"I don't have that specific code in my database, but here's a recommended "
378+
"VWC learning path based on general military experience:\n\n" +
379+
"\n".join(f"{i + 1}. {focus}" for i, focus in enumerate(translation['data']['tech_focus']))
369380
)
370-
381+
371382
return None
372383

384+
373385
def initialize_chat():
374386
"""Initialize the chat with a VWC-focused welcome message."""
375387
welcome_message = {
@@ -396,24 +408,25 @@ def initialize_chat():
396408
}
397409
return [welcome_message]
398410

411+
399412
def main():
400413
"""Main application function."""
401414
st.title("🇺🇸 VetsAI: Vets Who Code Assistant")
402-
415+
403416
# Initialize session
404417
if 'session_id' not in st.session_state:
405418
st.session_state.session_id = hashlib.md5(str(time.time()).encode()).hexdigest()
406-
419+
407420
if 'job_codes' not in st.session_state:
408421
try:
409422
st.session_state.job_codes = load_military_job_codes()
410423
except Exception as e:
411424
logger.error(f"Error loading job codes: {e}")
412425
st.session_state.job_codes = {}
413-
426+
414427
if 'messages' not in st.session_state:
415428
st.session_state.messages = initialize_chat()
416-
429+
417430
# Sidebar with VWC tech stack resources
418431
with st.sidebar:
419432
st.markdown("""
@@ -481,7 +494,7 @@ def main():
481494
"content": (
482495
"You are a specialized AI assistant for Vets Who Code members, designed to provide clear, practical technical guidance "
483496
"to veterans transitioning into software development careers.\n\n"
484-
497+
485498
"CORE TECH STACK:\n"
486499
"- Frontend: JavaScript, TypeScript, React, Next.js\n"
487500
"- Styling: CSS, Tailwind CSS\n"
@@ -490,40 +503,40 @@ def main():
490503
"- Advanced: AI/ML fundamentals\n"
491504
"- Development Tools: Git, GitHub, VS Code\n"
492505
"- Testing: Jest, Pytest\n\n"
493-
506+
494507
"CAREER TRANSITION GUIDANCE:\n"
495508
"1. Resume Development:\n"
496509
" - Technical Skills: Programming Languages, Frameworks, Tools, Cloud, Testing\n"
497510
" - Military Experience Translation: Leadership, Problem-solving, Team Collaboration\n\n"
498-
511+
499512
"2. Portfolio Development:\n"
500513
" - Clean code and documentation\n"
501514
" - Version control and API integration\n"
502515
" - Responsive design and performance\n"
503516
" - Testing and TypeScript implementation\n"
504517
" - Security and accessibility standards\n\n"
505-
518+
506519
"LEARNING PATHS:\n"
507520
"1. Fundamentals: HTML, CSS, JavaScript, Git\n"
508521
"2. Intermediate: TypeScript, React, Python\n"
509522
"3. Advanced: Next.js, FastAPI, Streamlit, AI/ML\n\n"
510-
523+
511524
"PROJECT FOCUS:\n"
512525
"1. Portfolio Projects: Personal website, APIs, Data visualization\n"
513526
"2. Technical Skills: Code quality, Testing, Security, Performance\n"
514527
"3. Career Materials: GitHub profile, Technical blog, Documentation\n\n"
515-
528+
516529
"Remember: Provide practical guidance for building technical skills and transitioning to software development careers. "
517530
"Focus on concrete examples and best practices."
518531
)
519532
})
520-
521-
response = get_chat_response(messages)
522-
st.markdown(response)
523-
st.session_state.messages.append({
524-
"role": "assistant",
525-
"content": response
526-
})
533+
with st.spinner("Building a response..."):
534+
response = get_chat_response(messages)
535+
st.markdown(response)
536+
st.session_state.messages.append({
537+
"role": "assistant",
538+
"content": response
539+
})
527540
except Exception as e:
528541
st.error(f"Error generating response: {str(e)}")
529542

@@ -547,5 +560,6 @@ def main():
547560
save_feedback(feedback)
548561
st.success("Thank you for your feedback!")
549562

563+
550564
if __name__ == "__main__":
551-
main()
565+
main()

run.sh

100644100755
File mode changed.

streamlit.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)