PageSourceSearch

https://careers.ccs.spokane.edu/assets/sites/controllers/chatbot/window_controller-cd843c1e.js

js spokane.edu collected 2026-09-24 09:43:30 UTC 4,507 bytes, 154 lines download raw bytes

1import {Controller} from "@hotwired/stimulus"
2
3export default class extends Controller {
4  static entityMap = {
5    '&': '&',
6    '<': '&lt;',
7    '>': '&gt;',
8    '"': '&quot;',
9    "'": '&#39;',
10    '/': '&#x2F;',
11    '`': '&#x60;',
12    '=': '&#x3D;'
13  };
14
15  static targets = ['form', 'message', 'hiddenJobIdInput', 'hiddenMessageContentKindInput', 'sendButton', 'candidateMessageTemplate', 'overlay', 'overlayContentContainer']
16
17  static values = {conversationState: String, giveUpTimeout: Number}
18
19  connect() {
20    this.conversationStateWriteObserver = new MutationObserver(() => this.conversationStateValueWritten())
21    this.conversationStateWriteObserver.observe(this.element, {attributeFilter: ['data-chatbot--window-conversation-state-value']})
22  }
23
24  disconnect() {
25    this.conversationStateWriteObserver.disconnect()
26    this.clearGiveUpTimer()
27  }
28
29  conversationStateValueChanged() {
30    this.syncSendButton()
31    this.clearGiveUpTimer()
32    if (this.replyInProgress()) {
33      this.startGiveUpTimer()
34    }
35  }
36
37  // Unlike Stimulus's conversationStateValueChanged, this also fires when a streamed chunk rewrites the same value
38  conversationStateValueWritten() {
39    if (this.replyInProgress()) {
40      this.restartGiveUpTimer()
41    }
42  }
43
44  restartGiveUpTimer() {
45    this.clearGiveUpTimer()
46    this.startGiveUpTimer()
47  }
48
49  startGiveUpTimer() {
50    if (this.hasGiveUpTimeoutValue) {
51      this.giveUpTimer = setTimeout(() => this.abandonReply(), this.giveUpTimeoutValue)
52    }
53  }
54
55  abandonReply() {
56    this.conversationStateValue = 'idle'
57  }
58
59  clearGiveUpTimer() {
60    clearTimeout(this.giveUpTimer)
61  }
62
63  formSubmit(e) {
64    if (this.emptyMessage() || this.sendButtonTarget.disabled) {
65      e.preventDefault()
66    }
67  }
68
69  turboSubmit(e) {
70    this.conversationStateValue = 'awaiting'
71    this.renderTurboStreamWithCandidateMessage()
72    this.resetForm()
73  }
74
75  endSubmit(e) {
76    if (!e.detail.success) {
77      this.conversationStateValue = 'idle'
78    }
79  }
80
81  syncSendButton() {
82    if (this.hasSendButtonTarget) {
83      this.sendButtonTarget.disabled = this.replyInProgress()
84    }
85  }
86
87  replyInProgress() {
88    return this.conversationStateValue !== 'idle'
89  }
90
91  renderTurboStreamWithCandidateMessage() {
92    const turboStreamsWithPlaceholders = this.candidateMessageTemplateTarget.content.querySelectorAll('turbo-stream:not([action="playNotification"])')
93    for (const turboStreamWithPlaceholder of turboStreamsWithPlaceholders) {
94      const tempTurboStreamContainer = document.createElement('div')
95      tempTurboStreamContainer.innerHTML = turboStreamWithPlaceholder.outerHTML
96        .replace('{{message_text}}', this.escapeHtml(this.messageTarget.value))
97        .replace('{{message_label}}', this.escapeHtml(this.messageTarget.dataset.messageLabel))
98        .replace('{{messageContentKind}}', this.escapeHtml(this.hiddenMessageContentKindInputTarget.value.replaceAll('_', '-')))
99        .replace('{{messageIconClass}}', this.escapeHtml(this.messageIconClass || ''))
100      Turbo.renderStreamMessage(tempTurboStreamContainer.innerHTML)
101    }
102  }
103
104  resetForm() {
105    this.messageTarget.value = ''
106    this.hiddenMessageContentKindInputTarget.value = this.hiddenMessageContentKindInputTarget.dataset.defaultValue
107    this.messageIconClass = ''
108    this.toggleSubmitButtonVisibility()
109  }
110
111  toggleSubmitButtonVisibility() {
112    this.sendButtonTarget.classList.toggle('d-none', this.emptyMessage())
113  }
114
115  emptyMessage() {
116    return this.messageTarget.value.trim().length === 0
117  }
118
119  keyEnterEvent(e) {
120    e.preventDefault()
121    this.formTarget.requestSubmit()
122  }
123
124  focusInputText() {
125    this.formTarget.classList.add('active')
126  }
127
128  blurInputText() {
129    this.formTarget.classList.remove('active')
130  }
131
132  closeOverlay() {
133    this.overlayTarget.classList.add('d-none')
134    this.overlayContentContainerTarget.innerHTML = ""
135  }
136
137  sendButtonDataAsMessage({currentTarget: {dataset: {message, jobId, messageContentKind, messageIconClass}}}) {
138    this.messageTarget.value = message
139    this.hiddenJobIdInputTarget.value = jobId
140    if (messageContentKind) {
141      this.hiddenMessageContentKindInputTarget.value = messageContentKind
142    }
143    this.messageIconClass = messageIconClass
144    this.formTarget.requestSubmit()
145
146    if (this.hasOverlayTarget && !this.overlayTarget.classList.contains('d-none')) {
147      this.closeOverlay()
148    }
149  }
150
151  escapeHtml(string) {
152    return String(string).replace(/[&<>"'`=\/]/g, (s) => this.constructor.entityMap[s])
153  }
154}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.