File size: 19,938 Bytes
dad7400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6b5231
 
dad7400
 
e6b5231
 
 
dad7400
e6b5231
 
 
dad7400
 
 
 
 
 
e6b5231
dad7400
e6b5231
dad7400
 
e6b5231
 
dad7400
 
e6b5231
 
dad7400
 
 
 
e6b5231
 
 
 
dad7400
 
e6b5231
 
 
dad7400
e6b5231
 
dad7400
 
 
 
e6b5231
 
 
 
dad7400
 
e6b5231
 
dad7400
e6b5231
dad7400
 
 
e6b5231
 
dad7400
e6b5231
dad7400
 
 
 
 
 
e6b5231
dad7400
e6b5231
dad7400
e6b5231
 
dad7400
 
e6b5231
dad7400
e6b5231
 
 
dad7400
e6b5231
 
 
 
 
 
 
 
 
dad7400
 
e6b5231
dad7400
 
 
 
 
 
 
 
 
 
e6b5231
dad7400
 
e6b5231
 
dad7400
 
e6b5231
dad7400
 
 
 
 
 
 
 
 
e6b5231
dad7400
 
 
 
 
 
 
e6b5231
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
import { useState, useRef, useEffect } from "react";
import { startNegotiation, sendResponse, acceptOffer } from "./utils/api";

function EQGauge({ label, value, max = 10 }) {
  const pct = Math.round((value / max) * 100);
  const color = pct >= 70 ? "#00b894" : pct >= 40 ? "#fdcb6e" : "#e17055";
  return (
    <div className="eq-gauge">
      <div className="eq-gauge-label">{label}</div>
      <div className="eq-gauge-bar-bg">
        <div className="eq-gauge-bar-fill" style={{ width: pct + "%", background: color }} />
      </div>
      <div className="eq-gauge-value">{value}/{max}</div>
    </div>
  );
}

function MomentBadge({ rating }) {
  const colors = { strong: "#00b894", neutral: "#fdcb6e", weak: "#e17055" };
  return <span className="moment-badge" style={{ background: colors[rating] || "#555", color: "#000", padding: "2px 10px", borderRadius: "12px", fontSize: "0.75rem", fontWeight: 600, marginLeft: "8px" }}>{rating}</span>;
}

function CoachingPanel({ tip, warning, candidateTactics, momentRating }) {
  if (!tip && !warning) return null;
  return (
    <div className="coaching-panel">
      <div className="coaching-header">Real-Time Coach{momentRating && <MomentBadge rating={momentRating} />}</div>
      {candidateTactics && candidateTactics.length > 0 && (
        <div className="coaching-tactics">
          <span className="coaching-tactics-label">Your tactics: </span>
          {candidateTactics.map((t, i) => <span key={i} className="tactic-pill">{t}</span>)}
        </div>
      )}
      {tip && <div className="coaching-tip"><strong>Try next:</strong> {tip}</div>}
      {warning && warning.length > 0 && <div className="coaching-warning"><strong>Avoid:</strong> {warning}</div>}
    </div>
  );
}

function ScoreCircle({ score, label, size = 80 }) {
  const r = (size - 10) / 2;
  const circ = 2 * Math.PI * r;
  const offset = circ - (score / 100) * circ;
  const color = score >= 75 ? "#00b894" : score >= 50 ? "#fdcb6e" : "#e17055";
  return (
    <div className="score-circle" style={{ width: size, height: size, position: "relative", display: "inline-block" }}>
      <svg width={size} height={size}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#2d2d44" strokeWidth="5" />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth="5"
          strokeDasharray={circ} strokeDashoffset={offset} strokeLinecap="round"
          transform={"rotate(-90 " + size/2 + " " + size/2 + ")"} />
      </svg>
      <div style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
        <div style={{ fontSize: size > 100 ? "2rem" : "1.1rem", fontWeight: 700, color: "#fff" }}>{score}</div>
        <div style={{ fontSize: "0.65rem", color: "#888" }}>{label}</div>
      </div>
    </div>
  );
}

function ReportView({ report, onReset }) {
  const r = report;
  return (
    <div className="app">
      <h1><span>Performance Report</span></h1>
      <div className="report">
        <div className="report-top-grid">
          <div className="report-main-score">
            <ScoreCircle score={r.overallScore || 0} label="Overall" size={120} />
            <div className="report-verdict">{r.verdict}</div>
            {r.letterGrade && <div className="report-grade">Grade: {r.letterGrade}</div>}
          </div>
          <div className="report-score-cards">
            <ScoreCircle score={r.emotionalIntelligence?.overall || 0} label="EQ" size={80} />
            <ScoreCircle score={r.communicationScore?.overall || 0} label="Comms" size={80} />
            <ScoreCircle score={(r.negotiationStyle?.effectiveness || 0) * 10} label="Style" size={80} />
          </div>
        </div>

        <div className="salary-comparison">
          <div className="salary-box"><div className="label">Target</div><div className="amount">{"$"}{(r.targetSalary||0).toLocaleString()}</div></div>
          <div className="salary-box result-box"><div className="label">Final Offer</div><div className="amount">{"$"}{(r.finalSalary||0).toLocaleString()}</div><div className="pct">{r.percentOfTarget}% of target</div></div>
          {r.marketContext && <div className="salary-box market-box"><div className="label">Market Range</div><div className="market-range">{"$"}{(r.marketContext.marketLow||0).toLocaleString()} - {"$"}{(r.marketContext.marketHigh||0).toLocaleString()}</div><div className="market-position">{r.marketContext.candidatePosition}</div></div>}
        </div>

        <p className="report-summary">{r.summary}</p>

        {r.negotiationStyle && <div className="report-section style-section"><h3>Negotiation Style: <span className="style-name">{r.negotiationStyle.primary}</span></h3><p>{r.negotiationStyle.description}</p></div>}

        {r.emotionalIntelligence && (
          <div className="report-section eq-section">
            <h3>Emotional Intelligence</h3>
            <p className="eq-analysis">{r.emotionalIntelligence.analysis}</p>
            <div className="eq-grid">
              <EQGauge label="Empathy" value={r.emotionalIntelligence.empathy}/>
              <EQGauge label="Assertiveness" value={r.emotionalIntelligence.assertiveness}/>
              <EQGauge label="Composure" value={r.emotionalIntelligence.composure}/>
              <EQGauge label="Rapport" value={r.emotionalIntelligence.rapport}/>
              <EQGauge label="Adaptability" value={r.emotionalIntelligence.adaptability}/>
            </div>
          </div>
        )}

        {r.communicationScore && (
          <div className="report-section">
            <h3>Communication</h3>
            <p className="eq-analysis">{r.communicationScore.analysis}</p>
            <div className="eq-grid">
              <EQGauge label="Clarity" value={r.communicationScore.clarity}/>
              <EQGauge label="Persuasiveness" value={r.communicationScore.persuasiveness}/>
              <EQGauge label="Active Listening" value={r.communicationScore.activeListening}/>
              <EQGauge label="Question Quality" value={r.communicationScore.questionQuality}/>
            </div>
          </div>
        )}

        {r.powerDynamics && (
          <div className="report-section power-section">
            <h3>Power Dynamics</h3>
            <div className="power-bars">
              <div className="power-row"><span className="power-label">You: {r.powerDynamics.candidatePower}/10</span><div className="power-bar-bg"><div className="power-bar-you" style={{width:(r.powerDynamics.candidatePower*10)+"%"}}/></div></div>
              <div className="power-row"><span className="power-label">Manager: {r.powerDynamics.managerPower}/10</span><div className="power-bar-bg"><div className="power-bar-mgr" style={{width:(r.powerDynamics.managerPower*10)+"%"}}/></div></div>
            </div>
            <p>{r.powerDynamics.assessment}</p>
            {r.powerDynamics.shiftMoments?.map((m,i)=><div key={i} className="power-shift">{m}</div>)}
          </div>
        )}

        {r.bestMoments?.length > 0 && (
          <div className="report-section">
            <h3>Best Moments</h3>
            {r.bestMoments.map((m,i)=><div key={i} className="moment-card best"><div className="moment-round">Round {m.round}</div><div className="moment-quote">"{m.quote}"</div><div className="moment-why">{m.why}</div></div>)}
          </div>
        )}

        {r.worstMoments?.length > 0 && (
          <div className="report-section">
            <h3>Weakest Moments</h3>
            {r.worstMoments.map((m,i)=><div key={i} className="moment-card worst"><div className="moment-round">Round {m.round}</div><div className="moment-quote">"{m.quote}"</div><div className="moment-why">{m.why}</div></div>)}
          </div>
        )}

        {r.tacticsUsed?.length > 0 && (
          <div className="report-section">
            <h3>Tactics Analysis</h3>
            <div className="tactics-grid">
              {r.tacticsUsed.map((t,i)=><div key={i} className={"tactic-card "+t.effectiveness}><div className="tactic-card-name">{t.name}</div><div className="tactic-card-eff">{t.effectiveness}</div><div className="tactic-card-example">"{t.example}"</div></div>)}
            </div>
          </div>
        )}

        {r.missedOpportunities?.length > 0 && (
          <div className="report-section">
            <h3>Missed Opportunities</h3>
            {r.missedOpportunities.map((m,i)=><div key={i} className="missed-card"><div className="missed-situation"><strong>Situation:</strong> {m.situation}</div><div className="missed-better"><strong>Better approach:</strong> {m.betterApproach}</div><div className="missed-impact"><strong>Impact:</strong> {m.impact}</div></div>)}
          </div>
        )}

        {r.marketContext && <div className="report-section"><h3>Market Context</h3><p>{r.marketContext.analysis}</p></div>}

        <div className="report-two-col">
          <div className="report-section"><h3>Strengths</h3><ul>{r.strengths?.map((s,i)=><li key={i}>{s}</li>)}</ul></div>
          <div className="report-section"><h3>Areas to Improve</h3><ul>{r.improvements?.map((s,i)=><li key={i}>{s}</li>)}</ul></div>
        </div>

        {r.personalizedTips?.length > 0 && (
          <div className="report-section">
            <h3>Personalized Pro Tips</h3>
            <div className="tips-grid">
              {r.personalizedTips.map((t,i)=><div key={i} className="tip-card"><div className="tip-category">{t.category}</div><div className="tip-text">{t.tip}</div></div>)}
            </div>
          </div>
        )}

        {r.nextSessionFocus && <div className="report-section next-focus"><h3>Next Session Focus</h3><p>{r.nextSessionFocus}</p></div>}

        <button className="btn btn-primary" onClick={onReset}>Practice Again</button>
      </div>
    </div>
  );
}

export default function App() {
  const [phase, setPhase] = useState("setup");
  const [config, setConfig] = useState({ role:"", company:"", currentSalary:"", targetSalary:"", difficulty:"medium", scenarioType:"salary" });
  const [sessionId, setSessionId] = useState(null);
  const [messages, setMessages] = useState([]);
  const [currentOffer, setCurrentOffer] = useState(0);
  const [response, setResponse] = useState("");
  const [loading, setLoading] = useState(false);
  const [report, setReport] = useState(null);
  const [round, setRound] = useState(1);
  const [tactic, setTactic] = useState("");
  const [tacticExplanation, setTacticExplanation] = useState("");
  const [error, setError] = useState("");
  const [coaching, setCoaching] = useState(null);
  const [eqScores, setEqScores] = useState(null);
  const [scenarioLabel, setScenarioLabel] = useState("");
  const chatRef = useRef(null);
  const inputRef = useRef(null);

  useEffect(() => { if (chatRef.current) chatRef.current.scrollTop = chatRef.current.scrollHeight; }, [messages, coaching]);
  useEffect(() => { if (phase === "negotiation" && !loading && inputRef.current) inputRef.current.focus(); }, [phase, loading, messages]);

  const handleStart = async () => {
    if (!config.role || !config.targetSalary) { setError("Role and target salary are required"); return; }
    setLoading(true); setError("");
    try {
      const data = await startNegotiation({ ...config, targetSalary: Number(config.targetSalary), currentSalary: Number(config.currentSalary) || undefined });
      setSessionId(data.sessionId); setCurrentOffer(data.initialOffer); setScenarioLabel(data.scenarioLabel || "Salary Negotiation");
      setMessages([{ role: "Hiring Manager", text: data.openingStatement + " Our initial offer is " + "$" + (data.initialOffer||0).toLocaleString() + ".", name: data.hiringManagerName }]);
      setPhase("negotiation");
    } catch (err) { setError(err.message); } finally { setLoading(false); }
  };

  const handleRespond = async () => {
    if (!response.trim()) return;
    setMessages(prev => [...prev, { role: "You", text: response }]);
    const myResponse = response; setResponse(""); setLoading(true); setCoaching(null);
    try {
      const data = await sendResponse(sessionId, myResponse);
      setCurrentOffer(data.currentOffer);
      setMessages(prev => [...prev, { role: "Hiring Manager", text: data.response }]);
      setRound(data.round);
      if (data.tactic) setTactic(data.tactic);
      if (data.tacticExplanation) setTacticExplanation(data.tacticExplanation);
      if (data.emotionalIntelligence) setEqScores(data.emotionalIntelligence);
      setCoaching({ tip: data.coachingTip, warning: data.coachingWarning, candidateTactics: data.candidateTactics, momentRating: data.momentRating });
    } catch (err) { setError(err.message); } finally { setLoading(false); }
  };

  const handleAccept = async () => {
    setLoading(true);
    try { const data = await acceptOffer(sessionId); setReport(data); setPhase("report"); }
    catch (err) { setError(err.message); } finally { setLoading(false); }
  };

  const resetAll = () => { setPhase("setup"); setMessages([]); setReport(null); setRound(1); setCoaching(null); setEqScores(null); setTactic(""); setTacticExplanation(""); setError(""); };

  if (phase === "report" && report) return <ReportView report={report} onReset={resetAll} />;

  if (phase === "negotiation") {
    return (
      <div className="app">
        <div className="neg-header"><h1><span>Negotiation Simulator</span></h1><div className="scenario-tag">{scenarioLabel}</div></div>
        <div className="offer-display">
          <div className="offer-meta">Round {round} | {config.difficulty} difficulty</div>
          <div className="offer-amount">{"$"}{(currentOffer||0).toLocaleString()}</div>
          <div className="offer-meta">Target: {"$"}{Number(config.targetSalary).toLocaleString()}</div>
          <div className="offer-progress-bar"><div className="offer-progress-fill" style={{ width: Math.min(100, Math.round((currentOffer / Number(config.targetSalary)) * 100)) + "%" }} /></div>
          <div className="offer-pct">{Math.round((currentOffer / Number(config.targetSalary)) * 100)}% of target</div>
          {tactic && <div className="tactic-badge-wrapper"><span className="tactic-badge">{tactic}</span>{tacticExplanation && <span className="tactic-explanation">{tacticExplanation}</span>}</div>}
        </div>
        {eqScores && <div className="eq-mini-dashboard"><div className="eq-mini-item"><span className="eq-mini-label">Empathy</span><span className="eq-mini-val">{eqScores.empathy}</span></div><div className="eq-mini-item"><span className="eq-mini-label">Assertive</span><span className="eq-mini-val">{eqScores.assertiveness}</span></div><div className="eq-mini-item"><span className="eq-mini-label">Composure</span><span className="eq-mini-val">{eqScores.composure}</span></div><div className="eq-mini-item"><span className="eq-mini-label">Rapport</span><span className="eq-mini-val">{eqScores.rapport}</span></div></div>}
        <div className="chat-container" ref={chatRef}>
          {messages.map((m,i) => <div key={i} className={"message " + (m.role==="You" ? "candidate" : "manager")}><div className="role">{m.role}{m.name ? " ("+m.name+")" : ""}</div><div className="text">{m.text}</div></div>)}
          {loading && <div className="loading"><div className="spinner"></div>Thinking...</div>}
        </div>
        {coaching && <CoachingPanel tip={coaching.tip} warning={coaching.warning} candidateTactics={coaching.candidateTactics} momentRating={coaching.momentRating} />}
        {error && <p className="error-text">{error}</p>}
        <div className="response-area">
          <textarea ref={inputRef} placeholder="Your response... (Enter to send, Shift+Enter for new line)" value={response} onChange={e => setResponse(e.target.value)} onKeyDown={e => { if (e.key==="Enter" && !e.shiftKey) { e.preventDefault(); handleRespond(); }}} disabled={loading} />
          <button className="btn btn-primary btn-send" onClick={handleRespond} disabled={loading || !response.trim()}>Send</button>
        </div>
        <div className="action-row">
          <button className="btn btn-accept" onClick={handleAccept} disabled={loading}>Accept Offer ({"$"}{(currentOffer||0).toLocaleString()})</button>
          <button className="btn-walk" onClick={handleAccept} disabled={loading}>Walk Away</button>
        </div>
      </div>
    );
  }

  return (
    <div className="app">
      <h1><span>Negotiation Simulator</span></h1>
      <p className="subtitle">Practice real-world negotiations with AI coaching. Get detailed performance analysis with emotional intelligence scoring.</p>
      {error && <p className="error-text">{error}</p>}
      <div className="setup-form">
        <div className="input-group"><label>Scenario Type</label>
          <div className="scenario-selector">
            {[{id:"salary",label:"Salary",desc:"Negotiate compensation"},{id:"promotion",label:"Promotion",desc:"Ask for a raise"},{id:"resources",label:"Resources",desc:"Project budget"},{id:"remote",label:"Remote Work",desc:"Flexibility terms"}].map(s =>
              <button key={s.id} className={"scenario-btn "+(config.scenarioType===s.id?"active":"")} onClick={()=>setConfig({...config,scenarioType:s.id})}>
                <span className="scenario-label">{s.label}</span>
                <span className="scenario-desc">{s.desc}</span>
              </button>
            )}
          </div>
        </div>
        <div className="form-row">
          <div className="input-group"><label>Job Role *</label><input placeholder="e.g., Senior Software Engineer" value={config.role} onChange={e=>setConfig({...config,role:e.target.value})}/></div>
          <div className="input-group"><label>Company (optional)</label><input placeholder="e.g., Google" value={config.company} onChange={e=>setConfig({...config,company:e.target.value})}/></div>
        </div>
        <div className="form-row">
          <div className="input-group"><label>Current Salary</label><input type="number" placeholder="e.g., 120000" value={config.currentSalary} onChange={e=>setConfig({...config,currentSalary:e.target.value})}/></div>
          <div className="input-group"><label>Target Salary *</label><input type="number" placeholder="e.g., 150000" value={config.targetSalary} onChange={e=>setConfig({...config,targetSalary:e.target.value})}/></div>
        </div>
        <div className="input-group"><label>Difficulty</label>
          <div className="difficulty-selector">
            {[{id:"easy",label:"Easy",desc:"Flexible manager"},{id:"medium",label:"Medium",desc:"Standard tactics"},{id:"hard",label:"Hard",desc:"Tough negotiator"}].map(d =>
              <button key={d.id} className={"diff-btn "+(config.difficulty===d.id?"active":"")+" diff-"+d.id} onClick={()=>setConfig({...config,difficulty:d.id})}>
                <span className="diff-label">{d.label}</span>
                <span className="diff-desc">{d.desc}</span>
              </button>
            )}
          </div>
        </div>
        <button className="btn btn-primary" onClick={handleStart} disabled={loading}>{loading ? "Starting..." : "Start Negotiation"}</button>
      </div>
      <div className="features-grid">
        <div className="feature-card"><div className="feature-title">EQ Scoring</div><div className="feature-desc">Real-time emotional intelligence feedback</div></div>
        <div className="feature-card"><div className="feature-title">Live Coaching</div><div className="feature-desc">Tips after each round on what to say next</div></div>
        <div className="feature-card"><div className="feature-title">Deep Analytics</div><div className="feature-desc">Style analysis, power dynamics, market context</div></div>
        <div className="feature-card"><div className="feature-title">4 Scenarios</div><div className="feature-desc">Salary, promotion, resources, remote work</div></div>
      </div>
    </div>
  );
}