@oneuptime/common 9.2.17 → 9.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/Server/API/AlertAPI.ts +139 -0
  2. package/Server/API/IncidentAPI.ts +132 -0
  3. package/Server/API/ScheduledMaintenanceAPI.ts +164 -0
  4. package/Server/Services/AIService.ts +0 -1
  5. package/Server/Services/IncidentService.ts +0 -1
  6. package/Server/Utils/AI/AlertAIContextBuilder.ts +264 -0
  7. package/Server/Utils/AI/IncidentAIContextBuilder.ts +212 -0
  8. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +345 -0
  9. package/Tests/Types/Domain.test.ts +24 -3
  10. package/Types/Domain.ts +21 -24
  11. package/UI/Components/AI/GenerateFromAIModal.tsx +157 -20
  12. package/build/dist/Server/API/AlertAPI.js +94 -0
  13. package/build/dist/Server/API/AlertAPI.js.map +1 -0
  14. package/build/dist/Server/API/IncidentAPI.js +88 -1
  15. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  16. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +103 -0
  17. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -0
  18. package/build/dist/Server/Services/AIService.js +0 -1
  19. package/build/dist/Server/Services/AIService.js.map +1 -1
  20. package/build/dist/Server/Services/IncidentService.js +0 -1
  21. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  22. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +238 -0
  23. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -0
  24. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +189 -0
  25. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  26. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +311 -0
  27. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -0
  28. package/build/dist/Tests/Types/Domain.test.js +19 -3
  29. package/build/dist/Tests/Types/Domain.test.js.map +1 -1
  30. package/build/dist/Types/Domain.js +18 -16
  31. package/build/dist/Types/Domain.js.map +1 -1
  32. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +116 -3
  33. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,345 @@
1
+ import ObjectID from "../../../Types/ObjectID";
2
+ import ScheduledMaintenance from "../../../Models/DatabaseModels/ScheduledMaintenance";
3
+ import ScheduledMaintenanceStateTimeline from "../../../Models/DatabaseModels/ScheduledMaintenanceStateTimeline";
4
+ import ScheduledMaintenanceInternalNote from "../../../Models/DatabaseModels/ScheduledMaintenanceInternalNote";
5
+ import ScheduledMaintenancePublicNote from "../../../Models/DatabaseModels/ScheduledMaintenancePublicNote";
6
+ import ScheduledMaintenanceService from "../../Services/ScheduledMaintenanceService";
7
+ import ScheduledMaintenanceStateTimelineService from "../../Services/ScheduledMaintenanceStateTimelineService";
8
+ import ScheduledMaintenanceInternalNoteService from "../../Services/ScheduledMaintenanceInternalNoteService";
9
+ import ScheduledMaintenancePublicNoteService from "../../Services/ScheduledMaintenancePublicNoteService";
10
+ import CaptureSpan from "../Telemetry/CaptureSpan";
11
+ import OneUptimeDate from "../../../Types/Date";
12
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
13
+ import { LLMMessage } from "../LLM/LLMService";
14
+
15
+ export interface ScheduledMaintenanceContextData {
16
+ scheduledMaintenance: ScheduledMaintenance;
17
+ stateTimeline: Array<ScheduledMaintenanceStateTimeline>;
18
+ internalNotes: Array<ScheduledMaintenanceInternalNote>;
19
+ publicNotes: Array<ScheduledMaintenancePublicNote>;
20
+ }
21
+
22
+ export interface AIGenerationContext {
23
+ contextText: string;
24
+ systemPrompt: string;
25
+ messages: Array<LLMMessage>;
26
+ }
27
+
28
+ export default class ScheduledMaintenanceAIContextBuilder {
29
+ @CaptureSpan()
30
+ public static async buildScheduledMaintenanceContext(data: {
31
+ scheduledMaintenanceId: ObjectID;
32
+ }): Promise<ScheduledMaintenanceContextData> {
33
+ const scheduledMaintenance: ScheduledMaintenance | null =
34
+ await ScheduledMaintenanceService.findOneById({
35
+ id: data.scheduledMaintenanceId,
36
+ select: {
37
+ _id: true,
38
+ title: true,
39
+ description: true,
40
+ createdAt: true,
41
+ startsAt: true,
42
+ endsAt: true,
43
+ customFields: true,
44
+ projectId: true,
45
+ currentScheduledMaintenanceState: {
46
+ name: true,
47
+ color: true,
48
+ },
49
+ monitors: {
50
+ name: true,
51
+ },
52
+ labels: {
53
+ name: true,
54
+ color: true,
55
+ },
56
+ },
57
+ props: {
58
+ isRoot: true,
59
+ },
60
+ });
61
+
62
+ if (!scheduledMaintenance) {
63
+ throw new Error("Scheduled Maintenance not found");
64
+ }
65
+
66
+ // Fetch state timeline
67
+ const stateTimeline: Array<ScheduledMaintenanceStateTimeline> =
68
+ await ScheduledMaintenanceStateTimelineService.findBy({
69
+ query: {
70
+ scheduledMaintenanceId: data.scheduledMaintenanceId,
71
+ },
72
+ select: {
73
+ _id: true,
74
+ createdAt: true,
75
+ startsAt: true,
76
+ endsAt: true,
77
+ scheduledMaintenanceState: {
78
+ name: true,
79
+ color: true,
80
+ },
81
+ createdByUser: {
82
+ name: true,
83
+ email: true,
84
+ },
85
+ },
86
+ sort: {
87
+ startsAt: SortOrder.Ascending,
88
+ },
89
+ limit: 100,
90
+ skip: 0,
91
+ props: {
92
+ isRoot: true,
93
+ },
94
+ });
95
+
96
+ // Fetch internal notes
97
+ const internalNotes: Array<ScheduledMaintenanceInternalNote> =
98
+ await ScheduledMaintenanceInternalNoteService.findBy({
99
+ query: {
100
+ scheduledMaintenanceId: data.scheduledMaintenanceId,
101
+ },
102
+ select: {
103
+ _id: true,
104
+ note: true,
105
+ createdAt: true,
106
+ createdByUser: {
107
+ name: true,
108
+ email: true,
109
+ },
110
+ },
111
+ sort: {
112
+ createdAt: SortOrder.Ascending,
113
+ },
114
+ limit: 100,
115
+ skip: 0,
116
+ props: {
117
+ isRoot: true,
118
+ },
119
+ });
120
+
121
+ // Fetch public notes
122
+ const publicNotes: Array<ScheduledMaintenancePublicNote> =
123
+ await ScheduledMaintenancePublicNoteService.findBy({
124
+ query: {
125
+ scheduledMaintenanceId: data.scheduledMaintenanceId,
126
+ },
127
+ select: {
128
+ _id: true,
129
+ note: true,
130
+ createdAt: true,
131
+ postedAt: true,
132
+ createdByUser: {
133
+ name: true,
134
+ email: true,
135
+ },
136
+ },
137
+ sort: {
138
+ createdAt: SortOrder.Ascending,
139
+ },
140
+ limit: 100,
141
+ skip: 0,
142
+ props: {
143
+ isRoot: true,
144
+ },
145
+ });
146
+
147
+ return {
148
+ scheduledMaintenance,
149
+ stateTimeline,
150
+ internalNotes,
151
+ publicNotes,
152
+ };
153
+ }
154
+
155
+ @CaptureSpan()
156
+ public static formatScheduledMaintenanceContextForNote(
157
+ contextData: ScheduledMaintenanceContextData,
158
+ noteType: "public" | "internal",
159
+ template?: string,
160
+ ): AIGenerationContext {
161
+ const { scheduledMaintenance, stateTimeline, internalNotes, publicNotes } =
162
+ contextData;
163
+
164
+ let contextText: string = "";
165
+
166
+ // Basic scheduled maintenance information
167
+ contextText += "# Scheduled Maintenance Information\n\n";
168
+ contextText += `**Title:** ${scheduledMaintenance.title || "N/A"}\n\n`;
169
+ contextText += `**Description:** ${scheduledMaintenance.description || "N/A"}\n\n`;
170
+ contextText += `**Current State:** ${scheduledMaintenance.currentScheduledMaintenanceState?.name || "N/A"}\n\n`;
171
+ contextText += `**Scheduled Start:** ${scheduledMaintenance.startsAt ? OneUptimeDate.getDateAsFormattedString(scheduledMaintenance.startsAt) : "N/A"}\n\n`;
172
+ contextText += `**Scheduled End:** ${scheduledMaintenance.endsAt ? OneUptimeDate.getDateAsFormattedString(scheduledMaintenance.endsAt) : "N/A"}\n\n`;
173
+ contextText += `**Created At:** ${scheduledMaintenance.createdAt ? OneUptimeDate.getDateAsFormattedString(scheduledMaintenance.createdAt) : "N/A"}\n\n`;
174
+
175
+ // Affected monitors
176
+ if (
177
+ scheduledMaintenance.monitors &&
178
+ scheduledMaintenance.monitors.length > 0
179
+ ) {
180
+ contextText += "**Affected Monitors:** ";
181
+ contextText += scheduledMaintenance.monitors
182
+ .map((m: { name?: string }) => {
183
+ return m.name;
184
+ })
185
+ .join(", ");
186
+ contextText += "\n\n";
187
+ }
188
+
189
+ // Labels
190
+ if (scheduledMaintenance.labels && scheduledMaintenance.labels.length > 0) {
191
+ contextText += "**Labels:** ";
192
+ contextText += scheduledMaintenance.labels
193
+ .map((l: { name?: string }) => {
194
+ return l.name;
195
+ })
196
+ .join(", ");
197
+ contextText += "\n\n";
198
+ }
199
+
200
+ // State timeline
201
+ if (stateTimeline.length > 0) {
202
+ contextText += "# State Timeline\n\n";
203
+ for (const timeline of stateTimeline) {
204
+ const startTime: string = timeline.startsAt
205
+ ? OneUptimeDate.getDateAsFormattedString(timeline.startsAt)
206
+ : "N/A";
207
+ const stateName: string =
208
+ timeline.scheduledMaintenanceState?.name?.toString() || "Unknown";
209
+ const createdBy: string =
210
+ timeline.createdByUser?.name?.toString() ||
211
+ timeline.createdByUser?.email?.toString() ||
212
+ "System";
213
+
214
+ contextText += `- **${startTime}**: State changed to **${stateName}** by ${createdBy}\n`;
215
+ }
216
+ contextText += "\n";
217
+ }
218
+
219
+ // Include internal notes for context (for both note types)
220
+ if (internalNotes.length > 0) {
221
+ contextText += "# Internal Notes (Private)\n\n";
222
+ for (const note of internalNotes) {
223
+ const noteTime: string = note.createdAt
224
+ ? OneUptimeDate.getDateAsFormattedString(note.createdAt)
225
+ : "N/A";
226
+ const createdBy: string =
227
+ note.createdByUser?.name?.toString() ||
228
+ note.createdByUser?.email?.toString() ||
229
+ "Unknown";
230
+
231
+ contextText += `**[${noteTime}] ${createdBy}:**\n`;
232
+ contextText += `${note.note || "N/A"}\n\n`;
233
+ }
234
+ }
235
+
236
+ // Public notes
237
+ if (publicNotes.length > 0) {
238
+ contextText += "# Public Notes\n\n";
239
+ for (const note of publicNotes) {
240
+ const noteTime: string = note.postedAt
241
+ ? OneUptimeDate.getDateAsFormattedString(note.postedAt)
242
+ : note.createdAt
243
+ ? OneUptimeDate.getDateAsFormattedString(note.createdAt)
244
+ : "N/A";
245
+ const createdBy: string =
246
+ note.createdByUser?.name?.toString() ||
247
+ note.createdByUser?.email?.toString() ||
248
+ "Unknown";
249
+
250
+ contextText += `**[${noteTime}] ${createdBy}:**\n`;
251
+ contextText += `${note.note || "N/A"}\n\n`;
252
+ }
253
+ }
254
+
255
+ // System prompt for note generation
256
+ let systemPrompt: string;
257
+
258
+ if (noteType === "public") {
259
+ if (template) {
260
+ systemPrompt = `You are an expert technical communicator. Your task is to fill in a public scheduled maintenance note template based on the provided maintenance event data.
261
+
262
+ CRITICAL INSTRUCTIONS:
263
+ - You MUST use ONLY the exact template structure provided below
264
+ - Fill in each section of the template with relevant information from the maintenance data
265
+ - Do NOT add any new sections, headers, or content that is not part of the template
266
+ - Do NOT add introductions, conclusions, or any text outside the template structure
267
+ - Write in a professional, clear, and customer-friendly manner
268
+ - Focus on what customers need to know: timing, impact, and what to expect
269
+ - Avoid technical jargon - keep it understandable for non-technical readers
270
+ - Be concise but informative
271
+
272
+ TEMPLATE TO FILL (use this exact structure):
273
+
274
+ ${template}`;
275
+ } else {
276
+ systemPrompt = `You are an expert technical communicator. Your task is to generate a public scheduled maintenance note that will be visible to customers on the status page.
277
+
278
+ The note should:
279
+ 1. Be written in a professional, customer-friendly tone
280
+ 2. Clearly communicate the current status of the maintenance
281
+ 3. Explain what work is being done and any impact on users
282
+ 4. Provide timing information (when it started, expected completion, etc.)
283
+ 5. Set appropriate expectations
284
+ 6. Be concise but informative
285
+
286
+ DO NOT include:
287
+ - Internal technical details that customers don't need
288
+ - Confidential information
289
+ - Excessive jargon
290
+
291
+ Write in markdown format for better readability.`;
292
+ }
293
+ } else if (template) {
294
+ // Internal note with template
295
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to fill in an internal scheduled maintenance note template based on the provided maintenance event data.
296
+
297
+ CRITICAL INSTRUCTIONS:
298
+ - You MUST use ONLY the exact template structure provided below
299
+ - Fill in each section of the template with relevant information from the maintenance data
300
+ - Do NOT add any new sections, headers, or content that is not part of the template
301
+ - Do NOT add introductions, conclusions, or any text outside the template structure
302
+ - Be technical and detailed - this is for the internal team
303
+ - Include relevant technical details, progress updates, and observations
304
+
305
+ TEMPLATE TO FILL (use this exact structure):
306
+
307
+ ${template}`;
308
+ } else {
309
+ // Internal note without template
310
+ systemPrompt = `You are an expert Site Reliability Engineer (SRE). Your task is to generate an internal scheduled maintenance note for the team.
311
+
312
+ The note should:
313
+ 1. Provide technical details about the maintenance progress
314
+ 2. Document observations, findings, or actions taken
315
+ 3. Include any issues encountered or changes to the plan
316
+ 4. Be detailed enough to help team members understand the current status
317
+ 5. Use technical language appropriate for the engineering team
318
+
319
+ Write in markdown format for better readability. Be thorough and technical.`;
320
+ }
321
+
322
+ // Build user message
323
+ const userMessage: string = template
324
+ ? `Fill in the template above using ONLY the following scheduled maintenance data. Output only the filled template, nothing else:\n\n${contextText}`
325
+ : `Based on the following scheduled maintenance data, please generate ${noteType === "public" ? "a customer-facing public" : "an internal technical"} maintenance note:\n\n${contextText}`;
326
+
327
+ // Build messages array
328
+ const messages: Array<LLMMessage> = [
329
+ {
330
+ role: "system",
331
+ content: systemPrompt,
332
+ },
333
+ {
334
+ role: "user",
335
+ content: userMessage,
336
+ },
337
+ ];
338
+
339
+ return {
340
+ contextText,
341
+ systemPrompt,
342
+ messages,
343
+ };
344
+ }
345
+ }
@@ -13,22 +13,43 @@ describe("class Domain", () => {
13
13
  expect(new Domain("example.ac").domain).toBe("example.ac");
14
14
  });
15
15
  test("new Domain() should throw the BadDataException if domain is invalid", () => {
16
+ // No dot in domain
16
17
  expect(() => {
17
18
  return new Domain("example");
18
19
  }).toThrowError(BadDataException);
19
20
  expect(() => {
20
21
  new Domain("example");
21
22
  }).toThrowError(BadDataException);
23
+
24
+ // Invalid characters
22
25
  expect(() => {
23
26
  new Domain("example@com");
24
27
  }).toThrowError(BadDataException);
25
28
 
29
+ // TLD with numbers (invalid - TLD must be letters only)
30
+ expect(() => {
31
+ new Domain("example.c0m");
32
+ }).toThrowError(BadDataException);
33
+
34
+ // Single letter TLD (invalid - TLD must be at least 2 characters)
26
35
  expect(() => {
27
- new Domain("example.invalid");
36
+ new Domain("example.c");
28
37
  }).toThrowError(BadDataException);
38
+
39
+ // Domain starting with hyphen
40
+ expect(() => {
41
+ new Domain("-example.com");
42
+ }).toThrowError(BadDataException);
43
+
44
+ // Domain ending with hyphen before TLD
45
+ expect(() => {
46
+ new Domain("example-.com");
47
+ }).toThrowError(BadDataException);
48
+
49
+ // Mutation to invalid domain
29
50
  expect(() => {
30
- const validDomain: Domain = new Domain("example.valid");
31
- validDomain.domain = "example.invalid";
51
+ const validDomain: Domain = new Domain("example.com");
52
+ validDomain.domain = "invalid";
32
53
  }).toThrowError(BadDataException);
33
54
  });
34
55
  test("Domain.domain should be mutable", () => {
package/Types/Domain.ts CHANGED
@@ -18,33 +18,30 @@ export default class Domain extends DatabaseProperty {
18
18
  }
19
19
 
20
20
  public static isValidDomain(domain: string): boolean {
21
- if (!domain.includes(".")) {
21
+ /*
22
+ * Regex-based domain validation
23
+ * - Each label (part between dots) must be 1-63 characters
24
+ * - Labels can contain alphanumeric characters and hyphens
25
+ * - Labels cannot start or end with a hyphen
26
+ * - TLD must be at least 2 characters and contain only letters
27
+ * - Total length should not exceed 253 characters
28
+ */
29
+
30
+ if (!domain || domain.length > 253) {
22
31
  return false;
23
32
  }
24
33
 
25
- const firstTLDs: Array<string> =
26
- "ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|be|bf|bg|bh|bi|bj|bm|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|cl|cm|cn|co|cr|cu|cv|cw|cx|cz|de|dj|dk|dm|do|dz|ec|ee|eg|es|et|eu|fi|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|im|in|io|iq|ir|is|it|je|jo|jp|kg|ki|km|kn|kp|kr|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|na|nc|ne|nf|ng|nl|no|nr|nu|nz|om|pa|pe|pf|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|yt".split(
27
- "|",
28
- );
29
- const secondTLDs: Array<string> =
30
- "ac|academy|accountant|accountants|actor|adult|aero|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh|lol|africa|top|coop".split(
31
- "|",
32
- );
33
-
34
- const parts: Array<string> = domain.split(".");
35
- const lastItem: string = parts[parts.length - 1] as string;
36
- const beforeLastItem: string = parts[parts.length - 2] as string;
37
-
38
- if (firstTLDs.includes(lastItem)) {
39
- if (secondTLDs.includes(beforeLastItem)) {
40
- return true;
41
- }
42
- return true;
43
- } else if (secondTLDs.includes(lastItem)) {
44
- return true;
45
- }
46
-
47
- return false;
34
+ /*
35
+ * Domain validation regex:
36
+ * ^ - start of string
37
+ * (?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+ - one or more labels followed by dot
38
+ * [a-zA-Z]{2,63} - TLD: 2-63 letters only
39
+ * $ - end of string
40
+ */
41
+ const domainRegex: RegExp =
42
+ /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/;
43
+
44
+ return domainRegex.test(domain);
48
45
  }
49
46
 
50
47
  public constructor(domain: string) {
@@ -20,6 +20,7 @@ export interface GenerateFromAIModalProps {
20
20
  onGenerate: (data: GenerateAIRequestData) => Promise<string>;
21
21
  onSuccess: (generatedContent: string) => void;
22
22
  templates?: Array<{ id: string; name: string; content?: string }>;
23
+ noteType?: NoteType; // Type of note being generated (determines default templates)
23
24
  }
24
25
 
25
26
  export interface GenerateAIRequestData {
@@ -27,13 +28,19 @@ export interface GenerateAIRequestData {
27
28
  templateId?: string;
28
29
  }
29
30
 
31
+ // Template categories for different note types
32
+ export type NoteType = "postmortem" | "public-note" | "internal-note";
33
+
30
34
  // Default hardcoded templates for incident postmortem
31
- const DEFAULT_TEMPLATES: Array<{ id: string; name: string; content: string }> =
32
- [
33
- {
34
- id: "default-standard",
35
- name: "Standard Postmortem",
36
- content: `## Executive Summary
35
+ const POSTMORTEM_TEMPLATES: Array<{
36
+ id: string;
37
+ name: string;
38
+ content: string;
39
+ }> = [
40
+ {
41
+ id: "default-standard",
42
+ name: "Standard Postmortem",
43
+ content: `## Executive Summary
37
44
  [Brief overview of the incident, its impact, and resolution]
38
45
 
39
46
  ## Incident Timeline
@@ -59,11 +66,11 @@ const DEFAULT_TEMPLATES: Array<{ id: string; name: string; content: string }> =
59
66
 
60
67
  ## Lessons Learned
61
68
  [Key takeaways and improvements identified]`,
62
- },
63
- {
64
- id: "default-detailed",
65
- name: "Detailed Technical Postmortem",
66
- content: `## Incident Overview
69
+ },
70
+ {
71
+ id: "default-detailed",
72
+ name: "Detailed Technical Postmortem",
73
+ content: `## Incident Overview
67
74
  **Incident Title**: [Title]
68
75
  **Severity**: [P1/P2/P3/P4]
69
76
  **Duration**: [Start time] - [End time]
@@ -125,11 +132,11 @@ const DEFAULT_TEMPLATES: Array<{ id: string; name: string; content: string }> =
125
132
 
126
133
  ## Appendix
127
134
  [Any additional technical details, logs, or graphs]`,
128
- },
129
- {
130
- id: "default-brief",
131
- name: "Brief Postmortem",
132
- content: `## What Happened
135
+ },
136
+ {
137
+ id: "default-brief",
138
+ name: "Brief Postmortem",
139
+ content: `## What Happened
133
140
  [Concise description of the incident]
134
141
 
135
142
  ## Why It Happened
@@ -141,22 +148,152 @@ const DEFAULT_TEMPLATES: Array<{ id: string; name: string; content: string }> =
141
148
  ## How We Prevent It
142
149
  - [ ] [Prevention action 1]
143
150
  - [ ] [Prevention action 2]`,
144
- },
145
- ];
151
+ },
152
+ ];
153
+
154
+ // Default templates for public notes (customer-facing)
155
+ const PUBLIC_NOTE_TEMPLATES: Array<{
156
+ id: string;
157
+ name: string;
158
+ content: string;
159
+ }> = [
160
+ {
161
+ id: "public-status-update",
162
+ name: "Status Update",
163
+ content: `## Current Status
164
+ [Brief description of the current situation]
165
+
166
+ ## What We're Doing
167
+ [Actions being taken to resolve the issue]
168
+
169
+ ## Next Update
170
+ [Expected time for next update or resolution]`,
171
+ },
172
+ {
173
+ id: "public-resolution",
174
+ name: "Resolution Notice",
175
+ content: `## Issue Resolved
176
+ [Brief description of what was resolved]
177
+
178
+ ## Summary
179
+ [What happened and how it was fixed]
180
+
181
+ ## Prevention
182
+ [Steps taken to prevent recurrence]
183
+
184
+ Thank you for your patience.`,
185
+ },
186
+ {
187
+ id: "public-maintenance",
188
+ name: "Maintenance Update",
189
+ content: `## Maintenance Status
190
+ [Current phase of the maintenance]
191
+
192
+ ## Progress
193
+ [What has been completed]
194
+
195
+ ## Remaining Work
196
+ [What still needs to be done]
197
+
198
+ ## Expected Completion
199
+ [Estimated completion time]`,
200
+ },
201
+ ];
202
+
203
+ // Default templates for internal notes (team-facing)
204
+ const INTERNAL_NOTE_TEMPLATES: Array<{
205
+ id: string;
206
+ name: string;
207
+ content: string;
208
+ }> = [
209
+ {
210
+ id: "internal-investigation",
211
+ name: "Investigation Update",
212
+ content: `## Current Investigation Status
213
+ [What we're looking at]
214
+
215
+ ## Findings So Far
216
+ - [Finding 1]
217
+ - [Finding 2]
218
+
219
+ ## Hypothesis
220
+ [Current theory about the root cause]
221
+
222
+ ## Next Steps
223
+ - [ ] [Action 1]
224
+ - [ ] [Action 2]`,
225
+ },
226
+ {
227
+ id: "internal-technical",
228
+ name: "Technical Analysis",
229
+ content: `## Technical Details
230
+ [Detailed technical observations]
231
+
232
+ ## Metrics/Logs
233
+ [Relevant metrics or log entries]
234
+
235
+ ## Impact Assessment
236
+ [Technical impact analysis]
237
+
238
+ ## Recommendations
239
+ [Technical recommendations for resolution]`,
240
+ },
241
+ {
242
+ id: "internal-handoff",
243
+ name: "Shift Handoff",
244
+ content: `## Current State
245
+ [Where things stand now]
246
+
247
+ ## Actions Taken
248
+ [What has been done so far]
249
+
250
+ ## Open Questions
251
+ [Things that still need investigation]
252
+
253
+ ## Immediate Priorities
254
+ - [ ] [Priority 1]
255
+ - [ ] [Priority 2]
256
+
257
+ ## Contacts
258
+ [Key people involved or to contact]`,
259
+ },
260
+ ];
261
+
262
+ // Function to get default templates based on note type
263
+ const getDefaultTemplates: (
264
+ noteType: NoteType,
265
+ ) => Array<{ id: string; name: string; content: string }> = (
266
+ noteType: NoteType,
267
+ ): Array<{ id: string; name: string; content: string }> => {
268
+ switch (noteType) {
269
+ case "postmortem":
270
+ return POSTMORTEM_TEMPLATES;
271
+ case "public-note":
272
+ return PUBLIC_NOTE_TEMPLATES;
273
+ case "internal-note":
274
+ return INTERNAL_NOTE_TEMPLATES;
275
+ default:
276
+ return POSTMORTEM_TEMPLATES;
277
+ }
278
+ };
146
279
 
147
280
  const GenerateFromAIModal: FunctionComponent<GenerateFromAIModalProps> = (
148
281
  props: GenerateFromAIModalProps,
149
282
  ): ReactElement => {
283
+ // Get default templates based on note type
284
+ const defaultTemplates: Array<{ id: string; name: string; content: string }> =
285
+ getDefaultTemplates(props.noteType || "postmortem");
286
+
150
287
  const [isGenerating, setIsGenerating] = useState<boolean>(false);
151
288
  const [error, setError] = useState<string>("");
152
289
  const [selectedTemplateId, setSelectedTemplateId] = useState<string>(
153
- DEFAULT_TEMPLATES[0]?.id || "",
290
+ defaultTemplates[0]?.id || "",
154
291
  );
155
292
  const [templateContent, setTemplateContent] = useState<string>("");
156
293
 
157
294
  // Combine default templates with custom templates
158
295
  const allTemplates: Array<{ id: string; name: string; content?: string }> = [
159
- ...DEFAULT_TEMPLATES,
296
+ ...defaultTemplates,
160
297
  ...(props.templates || []),
161
298
  ];
162
299