-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopenai_test.js
More file actions
166 lines (140 loc) · 4.96 KB
/
Copy pathopenai_test.js
File metadata and controls
166 lines (140 loc) · 4.96 KB
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
// Leave blank for the safe default (server-side OPENAI_API_KEY env var on the PHP host).
// For local dev only: paste a key here AND set DEV_ALLOW_CLIENT_KEY = true in openai_proxy.php.
// Never commit a real key. See README.md "API key & the OpenAI proxy".
const OPENAI_API_KEY = "";
// If your project uses a proxy / different base URL later, change this.
const OPENAI_URL = "/aims/openai_proxy.php";
const $ = (s) => document.querySelector(s);
function linesFromTextarea(v){
return String(v || "")
.split("\n")
.map(x => x.trim())
.filter(Boolean);
}
function buildPrompt(lines){
// Strong “don’t just list back” instruction + output shape.
const bullets = lines.map(l => `- ${l}`).join("\n");
return `
You are writing a brief, publication-ready executive summary of assessment findings.
STRICT RULES:
- Use ONLY the information in the FINDINGS.
- Paraphrase. Do NOT repeat the FINDINGS verbatim.
- Do NOT list the findings one-by-one.
- Do NOT invent causes, dates, locations, or background context.
- Keep it clear, professional, and concise.
FINDINGS:
${bullets}
Write EXACTLY this structure:
PARAGRAPH_1:
<2–3 sentences describing the overall story>
PARAGRAPH_2:
<2–3 sentences highlighting key changes and what it implies to verify next>
NEXT_CHECKS:
- <check 1>
- <check 2>
`.trim();
}
function extractText(respJson){
// Responses API: prefer output_text, otherwise try common shapes
if (respJson?.output_text) return respJson.output_text;
// Fallback: scan for any text fields
try {
const out = respJson?.output || [];
for (const item of out){
const content = item?.content || [];
for (const c of content){
if (c?.type === "output_text" && c?.text) return c.text;
}
}
} catch {}
return "";
}
function looksLikeJustBullets(out, inputLines){
const o = String(out || "").toLowerCase();
let hits = 0;
for (const l of inputLines){
const chunk = l.toLowerCase().slice(0, 40);
if (chunk.length >= 18 && o.includes(chunk)) hits++;
}
return hits >= 2; // too much verbatim reuse
}
async function summarize(){
const btn = $("#btn");
const status = $("#status");
const out = $("#out");
const lines = linesFromTextarea($("#input").value);
if (!lines.length){
out.textContent = "Please paste at least 2–3 insight lines.";
return;
}
btn.disabled = true;
status.textContent = "Calling OpenAI…";
out.textContent = "Working…";
try {
const prompt = buildPrompt(lines);
const r = await fetch(OPENAI_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
apiKey: OPENAI_API_KEY,
text: prompt
}),
});
const data = await r.json();
if (!r.ok){
out.textContent =
`HTTP ${r.status}\n` +
(data?.error?.message ? data.error.message : JSON.stringify(data, null, 2));
status.textContent = "Error.";
return;
}
const text = (extractText(data) || "").trim();
// If it "summarizes" by just echoing the bullets, do a tighter retry once.
if (!text || looksLikeJustBullets(text, lines)){
status.textContent = "Retrying with stricter paraphrase…";
const stricter = prompt + "\n\nIMPORTANT: If you copy any phrase longer than 6 words from FINDINGS, you FAIL.";
const r2 = await fetch(OPENAI_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4.1-mini",
input: stricter,
temperature: 0.1,
max_output_tokens: 280,
}),
});
const d2 = await r2.json();
if (r2.ok){
const t2 = (extractText(d2) || "").trim();
out.textContent = t2 || text || "No text returned.";
status.textContent = "Done.";
} else {
out.textContent = text || `HTTP ${r2.status}\n${JSON.stringify(d2, null, 2)}`;
status.textContent = "Done (retry failed).";
}
return;
}
out.textContent = text;
status.textContent = "Done.";
} catch (e){
out.textContent = String(e?.message || e);
status.textContent = "Error.";
} finally {
btn.disabled = false;
}
}
function loadExample(){
$("#input").value = [
"Largest drop: Grade 3 at Sequoia Elementary School (-6.6pp vs previous window)",
"Most consistent: Riverbend Elementary School (lowest variance), 118 rows",
"Top band shift: “not yet proficient” decreased most (-6.8pp)",
"Top current average: Grade 4 at Aspen Grove Elementary School (81.1%)",
"Largest gain: Grade 5 at Aspen Grove Elementary School (+2.2pp vs previous window)"
].join("\n");
}
$("#btn").addEventListener("click", summarize);
$("#btn-example").addEventListener("click", loadExample);
// hello, G! My favorite color is black :D