Most professionals walk into meetings under-prepared β not because they don't care, but because gathering context is genuinely tedious. You scan old email threads, dig through scattered notes across different apps, and mentally reconstruct what you promised someone two weeks ago. After the meeting, the same problem repeats in reverse: you scribble some notes, close the tab, and never look at them again. The next time you meet that person, you're starting from scratch.
MeetMind closes that loop. You get a structured AI briefing before walking in, and you can log what happened in under a minute before you forget it.
MeetMind works in two phases β before the meeting and after it β connected by a memory layer that makes each meeting smarter than the last.
Before the meeting, you type a contact's name and click "Generate my briefing." The app checks its memory for any past notes about that person, sends them along with the contact name to LLaMA 3.3 70B running on Groq, and returns a structured three-part briefing:
If there's no history, a "π No memory yet" badge tells you upfront β the output is a sensible first-meeting guide, not fabricated context.
After the meeting, you return to the same page, write freeform notes in a textarea β decisions made, budget agreed, follow-ups promised β and save them. Those notes become the context for the next briefing with that person. The loop:
Type a name β Generate briefing β Meet β Save notes β Richer briefing next time
A client you've met five times gets a briefing that references their preferences, their constraints, and the follow-up you promised last Tuesday. That's the difference between a tool someone uses once and one they keep open before every call.
The stack is intentionally lightweight: Flask backend, vanilla HTML/CSS/JS frontend, Groq API for inference, and a local JSON file for storage. No database, no login, no setup beyond an API key.
I owned the entire frontend: landing page, briefing form, notes form, AI response rendering, and every interaction state in between. That meant designing the layout, writing HTML/CSS, wiring JavaScript fetch calls to the Flask API, and making sure , success, error, and empty states were all handled clearly. The backend was handled separately β my job was making sure users never had to think about what was happening underneath.
The user journey is two actions: generate before, save after. Everything in the interface is designed to support those two moments and nothing else.
Before the meeting, friction had to be near zero. People check this right before a call, often rushing. The briefing form is one field and one button β no date pickers, no categories, no meeting type dropdowns. After the meeting, the notes form is equally minimal: a name field and a freeform textarea. The placeholder text β Budget discussed, follow-ups, preferences, key decisions...
β tells users what to write without any instructional copy.
Both sections live on the same scrollable page, briefing form on top and notes form below. That vertical order is itself an instruction: top to bottom maps directly to before to after. The physical layout communicates the temporal workflow without a word of explanation.
The frontend is a single Flask template (index.html
) with no JavaScript framework. Two sections, each with its own form and result container. State is entirely local: on submit, the button is disabled and relabelled "β³ Generating..." while the fetch fires, then the response is injected into a result div
.
The /get_briefing
response returns both the briefing text and a has_memory
boolean. That flag drives the "π No memory yet" badge β one value from the server, one conditional render on the client. Without that signal, users reading a generic first-meeting briefing would assume something was broken.
Descriptive button labels. "β¨ Generate my briefing" and "ποΈ Save to memory" tell the user exactly what each action does. "Submit" technically works but requires a beat of interpretation. At the scale of a two-action app, that beat matters.
The memory status badge. "No memory yet" isn't just informational β it's trust-building. It confirms the system is working, explains why the output is generic, and implicitly tells the user how to improve it: save notes after this meeting.
Inline success confirmation. The save message β "Successfully remembered details for priya!" β renders below the form and stays there. Not a toast that disappears in two seconds. Users can read it and move on knowing the save worked.
Structured AI output. The briefing prompt instructs the model to always follow a numbered three-section format. This was as much a frontend decision as a prompt decision β without predictable structure, rendering the response consistently is impossible, and scanning it in 30 seconds (the real usage context) becomes harder.
The briefing route returns both the text and has_memory
in one response β the frontend never needs a second request:
@app.route('/get_briefing', methods=['POST'])
def get_briefing():
contact = request.json.get('contact', '').strip()
if not contact:
return jsonify({"error": "Please enter a valid name."}), 400
history = hindsight_db.recall(contact)
briefing_text = generate_meeting_briefing(contact, history)
return jsonify({"briefing": briefing_text, "has_memory": len(history) > 0})
The AI prompt enforces a fixed output structure β this is the contract between the model and the UI:
user_prompt = f"""
Provide your output exactly in this structure:
1. SUMMARY OF PAST INTERACTION (Keep it short)
2. KEY REMINDERS (Promises made or things to look out for)
3. SUGGESTED CONVERSATION OPENERS
"""
The save route includes the contact name in the confirmation β so users see "Successfully remembered details for priya!" not just "Saved." That specificity matters when logging multiple contacts in one session:
if success:
return jsonify({"status": f"Successfully remembered details for {contact}!"})
My first layout placed both forms side by side in a two-column grid. Equal visual weight for two equally important actions seemed logical. In practice it was confusing β users couldn't tell which to use first. A horizontal layout implies parallel alternatives, not sequential steps. Switching to a vertical single-column stack fixed it without a word of copy. The layout became the instruction.
Rendering AI output. The briefing returns as plain text with asterisk bullet points. Parsing it into DOM elements is fragile β the model doesn't always format identically. Using white-space: pre-line
CSS to respect line breaks and keeping a fixed readable container width is simpler and far more resilient to output variation.
** state.** Briefing generation takes 1β2 seconds. Without an indicator, the UI looks frozen. Disabling the button and relabelling it "β³ Generating..." costs five lines of JavaScript and completely changes perceived responsiveness.
The biggest gap is mobile β the forms need larger tap targets and better spacing on small screens. There's also no onboarding; first-time users don't immediately understand that notes saved today improve next week's briefing. One sentence above the notes form would close that gap. Longer term, parsing the briefing into section cards with per-section copy buttons would make it easier to reference specific parts mid-meeting.
The most valuable decision on MeetMind was keeping the user flow to two steps. Everything else β the layout order, the button labels, the states, the inline confirmations, the memory badge β exists to support those two moments without friction. Interface complexity compounds. Every ambiguous state and silent failure adds cognitive cost. Keep the flow obvious, handle every state, make the output readable.
Live app: https://meetmind-iatt.onrender.com
Source code: https://github.com/RishitaVerma570/MeetMind
Hindsight memory layer: https://hindsight.vectorize.io
Vectorize agent memory: https://vectorize.io/what-is-agent-memory