codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
/**
|
|
3
|
+
* Ticket Chat Actions - handles sending messages, applying/discarding patches
|
|
4
|
+
*/
|
|
5
|
+
import { api, flash, splitMarkdownFrontmatter } from "./utils.js";
|
|
6
|
+
import { performTicketChatRequest } from "./ticketChatStream.js";
|
|
7
|
+
import { renderTicketMessages } from "./ticketChatEvents.js";
|
|
8
|
+
import { publish } from "./bus.js";
|
|
9
|
+
import { createDocChat } from "./docChatCore.js";
|
|
10
|
+
import { saveTicketChatHistory } from "./ticketChatStorage.js";
|
|
11
|
+
import { renderDiff } from "./diffRenderer.js";
|
|
12
|
+
// Limits for events display
|
|
13
|
+
export const TICKET_CHAT_EVENT_LIMIT = 8;
|
|
14
|
+
export const TICKET_CHAT_EVENT_MAX = 50;
|
|
15
|
+
export const ticketChat = createDocChat({
|
|
16
|
+
idPrefix: "ticket-chat",
|
|
17
|
+
storage: { keyPrefix: "car-ticket-chat-", maxMessages: 50, version: 1 },
|
|
18
|
+
limits: { eventVisible: TICKET_CHAT_EVENT_LIMIT, eventMax: TICKET_CHAT_EVENT_MAX },
|
|
19
|
+
styling: {
|
|
20
|
+
eventClass: "ticket-chat-event",
|
|
21
|
+
eventTitleClass: "ticket-chat-event-title",
|
|
22
|
+
eventSummaryClass: "ticket-chat-event-summary",
|
|
23
|
+
eventDetailClass: "ticket-chat-event-detail",
|
|
24
|
+
eventMetaClass: "ticket-chat-event-meta",
|
|
25
|
+
eventsEmptyClass: "ticket-chat-events-empty",
|
|
26
|
+
eventsWaitingClass: "ticket-chat-events-waiting",
|
|
27
|
+
eventsHiddenClass: "hidden",
|
|
28
|
+
messagesClass: "ticket-chat-message",
|
|
29
|
+
messageRoleClass: "ticket-chat-message-role",
|
|
30
|
+
messageContentClass: "ticket-chat-message-content",
|
|
31
|
+
messageMetaClass: "ticket-chat-message-meta",
|
|
32
|
+
messageUserClass: "user",
|
|
33
|
+
messageAssistantClass: "assistant",
|
|
34
|
+
messageAssistantThinkingClass: "thinking",
|
|
35
|
+
messageAssistantFinalClass: "final",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// Extend state with ticket-specific fields
|
|
39
|
+
export const ticketChatState = Object.assign(ticketChat.state, {
|
|
40
|
+
ticketIndex: null,
|
|
41
|
+
draft: null,
|
|
42
|
+
});
|
|
43
|
+
export function getTicketChatElements() {
|
|
44
|
+
const base = ticketChat.elements;
|
|
45
|
+
return {
|
|
46
|
+
input: base.input,
|
|
47
|
+
sendBtn: base.sendBtn,
|
|
48
|
+
voiceBtn: base.voiceBtn,
|
|
49
|
+
cancelBtn: base.cancelBtn,
|
|
50
|
+
newThreadBtn: base.newThreadBtn,
|
|
51
|
+
statusEl: base.statusEl,
|
|
52
|
+
streamEl: base.streamEl,
|
|
53
|
+
eventsMain: base.eventsMain,
|
|
54
|
+
eventsList: base.eventsList,
|
|
55
|
+
eventsCount: base.eventsCount,
|
|
56
|
+
eventsToggle: base.eventsToggle,
|
|
57
|
+
messagesEl: base.messagesEl,
|
|
58
|
+
// Content area elements - mutually exclusive with patch preview
|
|
59
|
+
contentTextarea: document.getElementById("ticket-editor-content"),
|
|
60
|
+
contentToolbar: document.getElementById("ticket-editor-toolbar"),
|
|
61
|
+
// Patch preview elements - mutually exclusive with content area
|
|
62
|
+
patchMain: document.getElementById("ticket-patch-main"),
|
|
63
|
+
patchBody: document.getElementById("ticket-patch-body"),
|
|
64
|
+
patchStatus: document.getElementById("ticket-patch-status"),
|
|
65
|
+
applyBtn: document.getElementById("ticket-patch-apply"),
|
|
66
|
+
discardBtn: document.getElementById("ticket-patch-discard"),
|
|
67
|
+
agentSelect: document.getElementById("ticket-chat-agent-select"),
|
|
68
|
+
modelSelect: document.getElementById("ticket-chat-model-select"),
|
|
69
|
+
reasoningSelect: document.getElementById("ticket-chat-reasoning-select"),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function resetTicketChatState() {
|
|
73
|
+
ticketChatState.status = "idle";
|
|
74
|
+
ticketChatState.error = "";
|
|
75
|
+
ticketChatState.streamText = "";
|
|
76
|
+
ticketChatState.statusText = "";
|
|
77
|
+
ticketChatState.controller = null;
|
|
78
|
+
// Note: events are cleared at the start of each new request, not here
|
|
79
|
+
// Messages persist across requests within the same ticket
|
|
80
|
+
}
|
|
81
|
+
export async function startNewTicketChatThread() {
|
|
82
|
+
if (ticketChatState.ticketIndex == null)
|
|
83
|
+
return;
|
|
84
|
+
const confirmed = window.confirm("Start a new conversation thread for this ticket?");
|
|
85
|
+
if (!confirmed)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
const key = `ticket_chat.${ticketChatState.ticketIndex}`;
|
|
89
|
+
await api(`/api/app-server/threads/reset`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: { key },
|
|
92
|
+
});
|
|
93
|
+
// Clear local message history
|
|
94
|
+
ticketChatState.messages = [];
|
|
95
|
+
saveTicketChatHistory(ticketChatState.ticketIndex, []);
|
|
96
|
+
clearTicketEvents();
|
|
97
|
+
flash("New thread started");
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
flash(`Failed to reset thread: ${err.message}`, "error");
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
renderTicketChat();
|
|
104
|
+
renderTicketMessages();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Clear events at the start of a new request.
|
|
109
|
+
* Events are transient (thinking/tool calls) and reset each turn.
|
|
110
|
+
*/
|
|
111
|
+
export function clearTicketEvents() {
|
|
112
|
+
ticketChat.clearEvents();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Add a user message to the chat history.
|
|
116
|
+
*/
|
|
117
|
+
export function addUserMessage(content) {
|
|
118
|
+
ticketChat.addUserMessage(content);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Add an assistant message to the chat history.
|
|
122
|
+
* Prevents duplicates by checking if the same content was just added.
|
|
123
|
+
*/
|
|
124
|
+
export function addAssistantMessage(content, isFinal = true) {
|
|
125
|
+
ticketChat.addAssistantMessage(content, isFinal);
|
|
126
|
+
}
|
|
127
|
+
export function setTicketIndex(index) {
|
|
128
|
+
const changed = ticketChatState.ticketIndex !== index;
|
|
129
|
+
ticketChatState.ticketIndex = index;
|
|
130
|
+
ticketChatState.draft = null;
|
|
131
|
+
resetTicketChatState();
|
|
132
|
+
// Clear chat history when switching tickets
|
|
133
|
+
if (changed) {
|
|
134
|
+
ticketChat.setTarget(index != null ? String(index) : null);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export function renderTicketChat() {
|
|
138
|
+
const els = getTicketChatElements();
|
|
139
|
+
// Shared chat render (status, events, messages)
|
|
140
|
+
ticketChat.render();
|
|
141
|
+
// MUTUALLY EXCLUSIVE: Show either the content editor OR the patch preview, never both.
|
|
142
|
+
// This prevents confusion about which view is the "current" state.
|
|
143
|
+
const hasDraft = !!ticketChatState.draft;
|
|
144
|
+
// Hide content area when showing patch preview
|
|
145
|
+
if (els.contentTextarea) {
|
|
146
|
+
els.contentTextarea.classList.toggle("hidden", hasDraft);
|
|
147
|
+
}
|
|
148
|
+
if (els.contentToolbar) {
|
|
149
|
+
els.contentToolbar.classList.toggle("hidden", hasDraft);
|
|
150
|
+
}
|
|
151
|
+
// Show patch preview only when there's a draft
|
|
152
|
+
if (els.patchMain) {
|
|
153
|
+
els.patchMain.classList.toggle("hidden", !hasDraft);
|
|
154
|
+
if (hasDraft) {
|
|
155
|
+
if (els.patchBody) {
|
|
156
|
+
renderDiff(ticketChatState.draft.patch || "(no changes)", els.patchBody);
|
|
157
|
+
}
|
|
158
|
+
if (els.patchStatus) {
|
|
159
|
+
els.patchStatus.textContent = ticketChatState.draft.agentMessage || "";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export async function sendTicketChat() {
|
|
165
|
+
const els = getTicketChatElements();
|
|
166
|
+
const message = (els.input?.value || "").trim();
|
|
167
|
+
if (!message) {
|
|
168
|
+
ticketChatState.error = "Enter a message to send.";
|
|
169
|
+
renderTicketChat();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (ticketChatState.status === "running") {
|
|
173
|
+
ticketChatState.error = "Ticket chat already running.";
|
|
174
|
+
renderTicketChat();
|
|
175
|
+
flash("Ticket chat already running", "error");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (ticketChatState.ticketIndex == null) {
|
|
179
|
+
ticketChatState.error = "No ticket selected.";
|
|
180
|
+
renderTicketChat();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
resetTicketChatState();
|
|
184
|
+
ticketChatState.status = "running";
|
|
185
|
+
ticketChatState.statusText = "queued";
|
|
186
|
+
ticketChatState.controller = new AbortController();
|
|
187
|
+
renderTicketChat();
|
|
188
|
+
if (els.input) {
|
|
189
|
+
els.input.value = "";
|
|
190
|
+
}
|
|
191
|
+
const agent = els.agentSelect?.value || "codex";
|
|
192
|
+
const model = els.modelSelect?.value || undefined;
|
|
193
|
+
const reasoning = els.reasoningSelect?.value || undefined;
|
|
194
|
+
try {
|
|
195
|
+
await performTicketChatRequest(ticketChatState.ticketIndex, message, ticketChatState.controller.signal, {
|
|
196
|
+
agent,
|
|
197
|
+
model,
|
|
198
|
+
reasoning,
|
|
199
|
+
});
|
|
200
|
+
// Try to load any pending draft
|
|
201
|
+
await loadTicketPending(ticketChatState.ticketIndex, true);
|
|
202
|
+
if (ticketChatState.status === "running") {
|
|
203
|
+
ticketChatState.status = "done";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const error = err;
|
|
208
|
+
if (error.name === "AbortError") {
|
|
209
|
+
ticketChatState.status = "interrupted";
|
|
210
|
+
ticketChatState.error = "";
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
ticketChatState.status = "error";
|
|
214
|
+
ticketChatState.error = error.message || "Ticket chat failed";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
ticketChatState.controller = null;
|
|
219
|
+
renderTicketChat();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export async function cancelTicketChat() {
|
|
223
|
+
if (ticketChatState.status !== "running")
|
|
224
|
+
return;
|
|
225
|
+
// Abort the request
|
|
226
|
+
if (ticketChatState.controller) {
|
|
227
|
+
ticketChatState.controller.abort();
|
|
228
|
+
}
|
|
229
|
+
// Send interrupt to server
|
|
230
|
+
if (ticketChatState.ticketIndex != null) {
|
|
231
|
+
try {
|
|
232
|
+
await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/interrupt`, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
// Ignore interrupt errors
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ticketChatState.status = "interrupted";
|
|
241
|
+
ticketChatState.error = "";
|
|
242
|
+
ticketChatState.statusText = "";
|
|
243
|
+
ticketChatState.controller = null;
|
|
244
|
+
renderTicketChat();
|
|
245
|
+
}
|
|
246
|
+
export async function applyTicketPatch() {
|
|
247
|
+
if (ticketChatState.ticketIndex == null) {
|
|
248
|
+
flash("No ticket selected", "error");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!ticketChatState.draft) {
|
|
252
|
+
flash("No draft to apply", "error");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const res = await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/apply`, { method: "POST" });
|
|
257
|
+
ticketChatState.draft = null;
|
|
258
|
+
flash("Draft applied");
|
|
259
|
+
// Notify that tickets changed
|
|
260
|
+
publish("tickets:updated", {});
|
|
261
|
+
// Update the editor textarea if content is returned
|
|
262
|
+
if (res.content) {
|
|
263
|
+
const textarea = document.getElementById("ticket-editor-content");
|
|
264
|
+
if (textarea) {
|
|
265
|
+
const [fmYaml, body] = splitMarkdownFrontmatter(res.content);
|
|
266
|
+
if (fmYaml !== null) {
|
|
267
|
+
textarea.value = body.trimStart();
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
textarea.value = res.content.trimStart();
|
|
271
|
+
}
|
|
272
|
+
// Trigger input event to update undo stack and autosave
|
|
273
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
const error = err;
|
|
279
|
+
flash(error.message || "Failed to apply draft", "error");
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
renderTicketChat();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export async function discardTicketPatch() {
|
|
286
|
+
if (ticketChatState.ticketIndex == null) {
|
|
287
|
+
flash("No ticket selected", "error");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/discard`, { method: "POST" });
|
|
292
|
+
ticketChatState.draft = null;
|
|
293
|
+
flash("Draft discarded");
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const error = err;
|
|
297
|
+
flash(error.message || "Failed to discard draft", "error");
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
renderTicketChat();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export async function loadTicketPending(index, silent = false) {
|
|
304
|
+
try {
|
|
305
|
+
const res = await api(`/api/tickets/${index}/chat/pending`, { method: "GET" });
|
|
306
|
+
ticketChatState.draft = {
|
|
307
|
+
patch: res.patch || "",
|
|
308
|
+
content: res.content || "",
|
|
309
|
+
agentMessage: res.agent_message || "",
|
|
310
|
+
createdAt: res.created_at || "",
|
|
311
|
+
baseHash: res.base_hash || "",
|
|
312
|
+
};
|
|
313
|
+
if (!silent) {
|
|
314
|
+
flash("Loaded pending draft");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
const error = err;
|
|
319
|
+
const message = error?.message || "";
|
|
320
|
+
if (message.includes("No pending")) {
|
|
321
|
+
ticketChatState.draft = null;
|
|
322
|
+
if (!silent) {
|
|
323
|
+
flash("No pending draft");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else if (!silent) {
|
|
327
|
+
flash(message || "Failed to load pending draft", "error");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
renderTicketChat();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { ticketChat } from "./ticketChatActions.js";
|
|
3
|
+
// This module now delegates to docChatCore for rendering and event parsing.
|
|
4
|
+
export function applyTicketEvent(payload) {
|
|
5
|
+
ticketChat.applyAppEvent(payload);
|
|
6
|
+
}
|
|
7
|
+
export function renderTicketEvents() {
|
|
8
|
+
ticketChat.renderEvents();
|
|
9
|
+
}
|
|
10
|
+
export function renderTicketMessages() {
|
|
11
|
+
ticketChat.renderMessages();
|
|
12
|
+
}
|
|
13
|
+
export function initTicketChatEvents() {
|
|
14
|
+
// Toggle already wired in docChatCore constructor.
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { clearChatHistory, loadChatHistory, saveChatHistory, } from "./docChatStorage.js";
|
|
3
|
+
const STORAGE_CONFIG = {
|
|
4
|
+
keyPrefix: "car-ticket-chat-",
|
|
5
|
+
maxMessages: 50,
|
|
6
|
+
version: 1,
|
|
7
|
+
};
|
|
8
|
+
export function saveTicketChatHistory(ticketIndex, messages) {
|
|
9
|
+
saveChatHistory(STORAGE_CONFIG, String(ticketIndex), messages);
|
|
10
|
+
}
|
|
11
|
+
export function loadTicketChatHistory(ticketIndex) {
|
|
12
|
+
return loadChatHistory(STORAGE_CONFIG, String(ticketIndex));
|
|
13
|
+
}
|
|
14
|
+
export function clearTicketChatHistory(ticketIndex) {
|
|
15
|
+
clearChatHistory(STORAGE_CONFIG, String(ticketIndex));
|
|
16
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
/**
|
|
3
|
+
* Ticket Chat Stream - handles SSE streaming for ticket chat
|
|
4
|
+
*/
|
|
5
|
+
import { resolvePath, getAuthToken } from "./utils.js";
|
|
6
|
+
import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, } from "./ticketChatActions.js";
|
|
7
|
+
import { applyTicketEvent, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
|
|
8
|
+
const decoder = new TextDecoder();
|
|
9
|
+
function parseMaybeJson(data) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(data);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function performTicketChatRequest(ticketIndex, message, signal, options = {}) {
|
|
18
|
+
// Clear events from previous request and add user message to history
|
|
19
|
+
clearTicketEvents();
|
|
20
|
+
addUserMessage(message);
|
|
21
|
+
// Render both chat (for container visibility) and messages
|
|
22
|
+
renderTicketChat();
|
|
23
|
+
renderTicketMessages();
|
|
24
|
+
const endpoint = resolvePath(`/api/tickets/${ticketIndex}/chat`);
|
|
25
|
+
const headers = {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
};
|
|
28
|
+
const token = getAuthToken();
|
|
29
|
+
if (token) {
|
|
30
|
+
headers.Authorization = `Bearer ${token}`;
|
|
31
|
+
}
|
|
32
|
+
const payload = {
|
|
33
|
+
message,
|
|
34
|
+
stream: true,
|
|
35
|
+
};
|
|
36
|
+
if (options.agent)
|
|
37
|
+
payload.agent = options.agent;
|
|
38
|
+
if (options.model)
|
|
39
|
+
payload.model = options.model;
|
|
40
|
+
if (options.reasoning)
|
|
41
|
+
payload.reasoning = options.reasoning;
|
|
42
|
+
const res = await fetch(endpoint, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers,
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
|
+
signal,
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
let detail = text;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(text);
|
|
53
|
+
detail = parsed.detail || parsed.error || text;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore parse errors
|
|
57
|
+
}
|
|
58
|
+
throw new Error(detail || `Request failed (${res.status})`);
|
|
59
|
+
}
|
|
60
|
+
const contentType = res.headers.get("content-type") || "";
|
|
61
|
+
if (contentType.includes("text/event-stream")) {
|
|
62
|
+
await readTicketChatStream(res);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Non-streaming response
|
|
66
|
+
const responsePayload = contentType.includes("application/json")
|
|
67
|
+
? await res.json()
|
|
68
|
+
: await res.text();
|
|
69
|
+
applyTicketChatResult(responsePayload);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function readTicketChatStream(res) {
|
|
73
|
+
if (!res.body)
|
|
74
|
+
throw new Error("Streaming not supported in this browser");
|
|
75
|
+
const reader = res.body.getReader();
|
|
76
|
+
let buffer = "";
|
|
77
|
+
let escapedNewlines = false;
|
|
78
|
+
for (;;) {
|
|
79
|
+
const { value, done } = await reader.read();
|
|
80
|
+
if (done)
|
|
81
|
+
break;
|
|
82
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
83
|
+
// Handle escaped newlines
|
|
84
|
+
if (!escapedNewlines) {
|
|
85
|
+
const combined = buffer + decoded;
|
|
86
|
+
if (!combined.includes("\n") && combined.includes("\\n")) {
|
|
87
|
+
escapedNewlines = true;
|
|
88
|
+
buffer = buffer.replace(/\\n(?=event:|data:|\\n)/g, "\n");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
buffer += escapedNewlines
|
|
92
|
+
? decoded.replace(/\\n(?=event:|data:|\\n)/g, "\n")
|
|
93
|
+
: decoded;
|
|
94
|
+
// Split on double newlines (SSE message delimiter)
|
|
95
|
+
const chunks = buffer.split("\n\n");
|
|
96
|
+
buffer = chunks.pop() || "";
|
|
97
|
+
for (const chunk of chunks) {
|
|
98
|
+
if (!chunk.trim())
|
|
99
|
+
continue;
|
|
100
|
+
let event = "message";
|
|
101
|
+
const dataLines = [];
|
|
102
|
+
chunk.split("\n").forEach((line) => {
|
|
103
|
+
if (line.startsWith("event:")) {
|
|
104
|
+
event = line.slice(6).trim();
|
|
105
|
+
}
|
|
106
|
+
else if (line.startsWith("data:")) {
|
|
107
|
+
dataLines.push(line.slice(5).trimStart());
|
|
108
|
+
}
|
|
109
|
+
else if (line.trim()) {
|
|
110
|
+
dataLines.push(line);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (dataLines.length === 0)
|
|
114
|
+
continue;
|
|
115
|
+
const data = dataLines.join("\n");
|
|
116
|
+
handleTicketStreamEvent(event, data);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function handleTicketStreamEvent(event, rawData) {
|
|
121
|
+
const parsed = parseMaybeJson(rawData);
|
|
122
|
+
switch (event) {
|
|
123
|
+
case "status": {
|
|
124
|
+
const status = typeof parsed === "string"
|
|
125
|
+
? parsed
|
|
126
|
+
: parsed.status || "";
|
|
127
|
+
ticketChatState.statusText = status;
|
|
128
|
+
renderTicketChat();
|
|
129
|
+
renderTicketEvents();
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "token": {
|
|
133
|
+
const token = typeof parsed === "string"
|
|
134
|
+
? parsed
|
|
135
|
+
: parsed.token ||
|
|
136
|
+
parsed.text ||
|
|
137
|
+
rawData ||
|
|
138
|
+
"";
|
|
139
|
+
ticketChatState.streamText = (ticketChatState.streamText || "") + token;
|
|
140
|
+
if (!ticketChatState.statusText || ticketChatState.statusText === "queued") {
|
|
141
|
+
ticketChatState.statusText = "responding";
|
|
142
|
+
}
|
|
143
|
+
renderTicketChat();
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "update": {
|
|
147
|
+
applyTicketChatResult(parsed);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "event":
|
|
151
|
+
case "app-server": {
|
|
152
|
+
// App-server events (thinking, tool calls, etc.)
|
|
153
|
+
applyTicketEvent(parsed);
|
|
154
|
+
renderTicketEvents();
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "error": {
|
|
158
|
+
const message = typeof parsed === "object" && parsed !== null
|
|
159
|
+
? parsed.detail ||
|
|
160
|
+
parsed.error ||
|
|
161
|
+
rawData
|
|
162
|
+
: rawData || "Ticket chat failed";
|
|
163
|
+
ticketChatState.status = "error";
|
|
164
|
+
ticketChatState.error = String(message);
|
|
165
|
+
// Add error as assistant message
|
|
166
|
+
addAssistantMessage(`Error: ${message}`, true);
|
|
167
|
+
renderTicketChat();
|
|
168
|
+
renderTicketMessages();
|
|
169
|
+
throw new Error(String(message));
|
|
170
|
+
}
|
|
171
|
+
case "interrupted": {
|
|
172
|
+
const message = typeof parsed === "object" && parsed !== null
|
|
173
|
+
? parsed.detail || rawData
|
|
174
|
+
: rawData || "Ticket chat interrupted";
|
|
175
|
+
ticketChatState.status = "interrupted";
|
|
176
|
+
ticketChatState.error = "";
|
|
177
|
+
ticketChatState.statusText = String(message);
|
|
178
|
+
// Add interrupted message
|
|
179
|
+
addAssistantMessage("Request interrupted", true);
|
|
180
|
+
renderTicketChat();
|
|
181
|
+
renderTicketMessages();
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "done":
|
|
185
|
+
case "finish": {
|
|
186
|
+
ticketChatState.status = "done";
|
|
187
|
+
// Final render to ensure UI is up to date
|
|
188
|
+
renderTicketChat();
|
|
189
|
+
renderTicketMessages();
|
|
190
|
+
renderTicketEvents();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
// Unknown event - try to parse as app-server event
|
|
195
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
196
|
+
const messageObj = parsed;
|
|
197
|
+
if (messageObj.method || messageObj.message) {
|
|
198
|
+
applyTicketEvent(parsed);
|
|
199
|
+
renderTicketEvents();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function applyTicketChatResult(payload) {
|
|
206
|
+
if (!payload || typeof payload !== "object")
|
|
207
|
+
return;
|
|
208
|
+
const result = payload;
|
|
209
|
+
if (result.status === "interrupted") {
|
|
210
|
+
ticketChatState.status = "interrupted";
|
|
211
|
+
ticketChatState.error = "";
|
|
212
|
+
addAssistantMessage("Request interrupted", true);
|
|
213
|
+
renderTicketChat();
|
|
214
|
+
renderTicketMessages();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (result.status === "error" || result.error) {
|
|
218
|
+
ticketChatState.status = "error";
|
|
219
|
+
ticketChatState.error =
|
|
220
|
+
result.detail || result.error || "Chat failed";
|
|
221
|
+
addAssistantMessage(`Error: ${ticketChatState.error}`, true);
|
|
222
|
+
renderTicketChat();
|
|
223
|
+
renderTicketMessages();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Success
|
|
227
|
+
ticketChatState.status = "done";
|
|
228
|
+
if (result.message) {
|
|
229
|
+
ticketChatState.streamText = result.message;
|
|
230
|
+
}
|
|
231
|
+
if (result.agent_message || result.agentMessage) {
|
|
232
|
+
ticketChatState.statusText =
|
|
233
|
+
result.agent_message || result.agentMessage || "";
|
|
234
|
+
}
|
|
235
|
+
// Check for draft/patch in response
|
|
236
|
+
const hasDraft = result.has_draft ?? result.hasDraft;
|
|
237
|
+
if (hasDraft === false) {
|
|
238
|
+
ticketChatState.draft = null;
|
|
239
|
+
}
|
|
240
|
+
else if (hasDraft === true || result.draft || result.patch || result.content) {
|
|
241
|
+
ticketChatState.draft = {
|
|
242
|
+
content: result.content || "",
|
|
243
|
+
patch: result.patch || "",
|
|
244
|
+
agentMessage: result.agent_message || result.agentMessage || "",
|
|
245
|
+
createdAt: result.created_at || result.createdAt || "",
|
|
246
|
+
baseHash: result.base_hash || result.baseHash || "",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// Add assistant message from response
|
|
250
|
+
const responseText = ticketChatState.streamText ||
|
|
251
|
+
ticketChatState.statusText ||
|
|
252
|
+
(ticketChatState.draft ? "Changes ready to apply" : "Done");
|
|
253
|
+
if (responseText && ticketChatState.messages.length > 0) {
|
|
254
|
+
// Only add if we have messages (i.e., a user message was sent)
|
|
255
|
+
const lastMessage = ticketChatState.messages[ticketChatState.messages.length - 1];
|
|
256
|
+
// Avoid duplicate assistant messages
|
|
257
|
+
if (lastMessage.role === "user") {
|
|
258
|
+
addAssistantMessage(responseText, true);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
renderTicketChat();
|
|
262
|
+
renderTicketMessages();
|
|
263
|
+
renderTicketEvents();
|
|
264
|
+
}
|