@rogatio/cli 5.15.0 → 5.16.0

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 (2) hide show
  1. package/dist/editor/index.js +162 -16
  2. package/package.json +1 -1
@@ -231,7 +231,7 @@ function hasLoneSurrogate(value) {
231
231
  }
232
232
 
233
233
  // packages/editor/src/ai-assist-panel.ts
234
- function createAIAssistPanel(_container, _editor, onApply, onClose) {
234
+ function createAIAssistPanel(_container, _editor, onApply, onClose, onSend) {
235
235
  let visible = false;
236
236
  let streamingContent = "";
237
237
  let streamingMessageIndex = -1;
@@ -351,6 +351,7 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
351
351
  panel.appendChild(messagesContainer);
352
352
  panel.appendChild(inputContainer);
353
353
  const closeBtn = header.querySelector(".ai-close-btn");
354
+ let sending = false;
354
355
  function show() {
355
356
  if (!visible) {
356
357
  visible = true;
@@ -373,10 +374,17 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
373
374
  onClose();
374
375
  }
375
376
  }
377
+ function destroy() {
378
+ visible = false;
379
+ if (panel.parentNode) {
380
+ panel.parentNode.removeChild(panel);
381
+ }
382
+ onClose();
383
+ }
376
384
  function isVisible() {
377
385
  return visible;
378
386
  }
379
- function addMessage(message) {
387
+ function renderMessage(message) {
380
388
  const messageEl = document.createElement("div");
381
389
  messageEl.style.cssText = `
382
390
  display: flex;
@@ -402,6 +410,10 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
402
410
  messagesContainer.appendChild(messageEl);
403
411
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
404
412
  }
413
+ function addMessage(message) {
414
+ messages.push(message);
415
+ renderMessage(message);
416
+ }
405
417
  function createProposalElement(proposal) {
406
418
  const container = document.createElement("div");
407
419
  container.style.cssText = `
@@ -513,22 +525,22 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
513
525
  }
514
526
  function updateStreamingContent(content) {
515
527
  if (streamingMessageIndex >= 0 && streamingMessageIndex < messages.length) {
528
+ streamingContent += content;
516
529
  const message = messages[streamingMessageIndex];
517
- message.content = streamingContent + content;
518
- const bubbles = messagesContainer.querySelectorAll(
519
- "div > div:first-child"
520
- );
521
- if (bubbles[streamingMessageIndex]) {
522
- bubbles[streamingMessageIndex].textContent = message.content;
530
+ message.content = streamingContent;
531
+ const messageEl = messagesContainer.children[streamingMessageIndex];
532
+ const bubble = messageEl?.firstElementChild;
533
+ if (bubble instanceof HTMLElement) {
534
+ bubble.textContent = message.content;
523
535
  }
536
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
524
537
  }
525
538
  }
526
539
  function startStreaming(role) {
527
540
  streamingContent = "";
528
541
  const message = { role, content: "", isStreaming: true };
529
- messages.push(message);
530
- streamingMessageIndex = messages.length - 1;
531
542
  addMessage(message);
543
+ streamingMessageIndex = messages.length - 1;
532
544
  }
533
545
  function finishStreaming(proposal) {
534
546
  if (streamingMessageIndex >= 0 && streamingMessageIndex < messages.length) {
@@ -552,9 +564,16 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
552
564
  }
553
565
  function handleSend() {
554
566
  const prompt = textarea.value.trim();
555
- if (!prompt) return;
567
+ if (!prompt || sending) return;
568
+ sending = true;
569
+ sendButton.disabled = true;
556
570
  textarea.value = "";
557
571
  textarea.style.height = "auto";
572
+ addMessage({ role: "user", content: prompt });
573
+ void Promise.resolve(onSend(prompt)).finally(() => {
574
+ sending = false;
575
+ sendButton.disabled = false;
576
+ });
558
577
  }
559
578
  closeBtn.addEventListener("click", hide);
560
579
  sendButton.addEventListener("click", handleSend);
@@ -567,6 +586,7 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
567
586
  return {
568
587
  show,
569
588
  hide,
589
+ destroy,
570
590
  isVisible,
571
591
  addMessage,
572
592
  clearMessages,
@@ -1966,6 +1986,69 @@ function displayName(value, fallback) {
1966
1986
  function isHTMLElement(value) {
1967
1987
  return value !== null && typeof value === "object" && value.nodeType === 1 && typeof value.appendChild === "function";
1968
1988
  }
1989
+ function isAsyncIterable(value) {
1990
+ return value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
1991
+ }
1992
+ function aiAssistErrorMessage(error) {
1993
+ if (error instanceof Error && error.message.length > 0) return error.message;
1994
+ if (typeof error === "string" && error.length > 0) return error;
1995
+ return "AI Assist failed.";
1996
+ }
1997
+ function ownString(record, ...keys) {
1998
+ for (const key of keys) {
1999
+ if (!Object.hasOwn(record, key)) continue;
2000
+ if (FORBIDDEN_EXTENSION_FIELDS.has(key)) continue;
2001
+ const value = record[key];
2002
+ if (typeof value === "string") return value;
2003
+ }
2004
+ return void 0;
2005
+ }
2006
+ function applyRuleProposalAction(rule, ruleProposal) {
2007
+ const action = ruleProposal.action;
2008
+ switch (ruleProposal.kind) {
2009
+ case "redirect": {
2010
+ rule.type = "redirect";
2011
+ const snap = snapshotOwnData(action);
2012
+ if (snap.valid) rule.redirect = snap.value;
2013
+ return;
2014
+ }
2015
+ case "query": {
2016
+ rule.type = "query";
2017
+ const snap = snapshotOwnData(action);
2018
+ if (snap.valid) rule.action = snap.value;
2019
+ return;
2020
+ }
2021
+ case "header": {
2022
+ rule.type = "header";
2023
+ if (!isRecord4(action)) return;
2024
+ const direction = ownString(action, "headerDirection", "direction");
2025
+ const operation = ownString(action, "headerOperation", "operation");
2026
+ const name = ownString(action, "headerName", "name");
2027
+ const value = ownString(action, "headerValue", "value");
2028
+ if (direction !== void 0) rule.headerDirection = direction;
2029
+ if (operation !== void 0) rule.headerOperation = operation;
2030
+ if (name !== void 0) rule.headerName = name;
2031
+ if (value !== void 0) rule.headerValue = value;
2032
+ return;
2033
+ }
2034
+ case "response-body": {
2035
+ rule.type = "response-body";
2036
+ const snap = snapshotOwnData(action);
2037
+ if (snap.valid) rule.responseBody = snap.value;
2038
+ return;
2039
+ }
2040
+ case "request-body": {
2041
+ rule.type = "request-body";
2042
+ const snap = snapshotOwnData(action);
2043
+ if (snap.valid) rule.requestBody = snap.value;
2044
+ return;
2045
+ }
2046
+ default: {
2047
+ const _exhaustive = ruleProposal.kind;
2048
+ void _exhaustive;
2049
+ }
2050
+ }
2051
+ }
1969
2052
  function normalizeExtensions(value) {
1970
2053
  const ids = /* @__PURE__ */ new Set();
1971
2054
  const merged = [...builtInRuleTypes];
@@ -2042,6 +2125,7 @@ var EditorControllerImpl = class {
2042
2125
  testRunning = false;
2043
2126
  testRequestId = 0;
2044
2127
  aiAssistPanel = null;
2128
+ aiAssistInFlight = false;
2045
2129
  constructor(options, initial, extensions) {
2046
2130
  this.root = options.root;
2047
2131
  this.document = options.root.ownerDocument;
@@ -2064,8 +2148,8 @@ var EditorControllerImpl = class {
2064
2148
  },
2065
2149
  (proposal) => this.applyAIProposal(proposal),
2066
2150
  () => {
2067
- }
2068
- // onClose
2151
+ },
2152
+ (prompt) => this.runAIAssist(prompt)
2069
2153
  );
2070
2154
  }
2071
2155
  this.host = this.document.createElement("div");
@@ -2142,7 +2226,7 @@ var EditorControllerImpl = class {
2142
2226
  if (this.destroyed) return;
2143
2227
  this.destroyed = true;
2144
2228
  if (this.aiAssistPanel) {
2145
- this.aiAssistPanel.hide();
2229
+ this.aiAssistPanel.destroy();
2146
2230
  this.aiAssistPanel = null;
2147
2231
  }
2148
2232
  this.cleanupExtensions();
@@ -2916,8 +3000,7 @@ var EditorControllerImpl = class {
2916
3000
  if (ruleProposal.method) {
2917
3001
  rule.method = ruleProposal.method;
2918
3002
  }
2919
- const actionField = ruleProposal.kind === "redirect" ? ruleProposal.kind : "action";
2920
- rule[actionField] = ruleProposal.action;
3003
+ applyRuleProposalAction(rule, ruleProposal);
2921
3004
  group.rules.push(rule);
2922
3005
  this.markChanged();
2923
3006
  const groupIdStr = String(group.id);
@@ -2932,6 +3015,69 @@ var EditorControllerImpl = class {
2932
3015
  this.statusMessage = `Applied ${proposal.rules.length} rule${proposal.rules.length === 1 ? "" : "s"} from AI.`;
2933
3016
  this.render();
2934
3017
  }
3018
+ async runAIAssist(prompt) {
3019
+ const handler = this.options.aiAssist;
3020
+ const panel = this.aiAssistPanel;
3021
+ if (!handler || !panel || this.aiAssistInFlight || this.destroyed) return;
3022
+ this.aiAssistInFlight = true;
3023
+ const diagnostics = this.validateCurrent();
3024
+ const hasErrors = diagnostics.some(
3025
+ (diagnostic2) => diagnostic2.severity === "error"
3026
+ );
3027
+ const request = {
3028
+ kind: hasErrors ? "fix" : "generate",
3029
+ prompt,
3030
+ context: {
3031
+ project: this.getDraft(),
3032
+ activeGroupId: this.currentGroupId(),
3033
+ diagnostics: diagnostics.length > 0 ? diagnostics : void 0
3034
+ }
3035
+ };
3036
+ panel.startStreaming("assistant");
3037
+ try {
3038
+ const result = handler(request);
3039
+ if (isAsyncIterable(result)) {
3040
+ let proposal;
3041
+ for await (const chunk of result) {
3042
+ if (this.destroyed) return;
3043
+ const handled = this.consumeAIAssistChunk(panel, chunk);
3044
+ if (handled === "error") return;
3045
+ if (handled.proposal) proposal = handled.proposal;
3046
+ }
3047
+ panel.finishStreaming(proposal);
3048
+ return;
3049
+ }
3050
+ const response = await result;
3051
+ if (this.destroyed) return;
3052
+ panel.finishStreaming(response.proposal);
3053
+ } catch (error) {
3054
+ if (this.destroyed) return;
3055
+ panel.finishStreaming();
3056
+ panel.addMessage({
3057
+ role: "system",
3058
+ content: aiAssistErrorMessage(error)
3059
+ });
3060
+ } finally {
3061
+ this.aiAssistInFlight = false;
3062
+ }
3063
+ }
3064
+ consumeAIAssistChunk(panel, chunk) {
3065
+ if (chunk.type === "token") {
3066
+ if (typeof chunk.content === "string" && chunk.content.length > 0) {
3067
+ panel.updateStreamingContent(chunk.content);
3068
+ }
3069
+ return {};
3070
+ }
3071
+ if (chunk.type === "error") {
3072
+ panel.finishStreaming();
3073
+ panel.addMessage({
3074
+ role: "system",
3075
+ content: chunk.error?.message ?? "AI Assist failed."
3076
+ });
3077
+ return "error";
3078
+ }
3079
+ return { proposal: chunk.proposal };
3080
+ }
2935
3081
  addGroupOrigin(groupId) {
2936
3082
  const group = this.groupById(groupId);
2937
3083
  if (!group || this.saving) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "5.15.0",
3
+ "version": "5.16.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local-first browser request and response rules — editor host, file verification, test runner, and runtime dispatch.",