clay-server 3.6.0 → 3.7.0-beta.1
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.
- package/lib/project-image.js +1 -1
- package/lib/project.js +1 -0
- package/lib/public/css/generated-images.css +225 -0
- package/lib/public/css/input.css +2 -77
- package/lib/public/index.html +2 -44
- package/lib/public/modules/app-messages.js +9 -33
- package/lib/public/modules/app-panels.js +0 -72
- package/lib/public/modules/generated-images.js +132 -0
- package/lib/public/modules/input.js +1 -10
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +2 -0
- package/lib/sdk-message-processor.js +57 -2
- package/lib/sessions.js +8 -6
- package/lib/yoke/adapters/codex.js +39 -0
- package/package.json +1 -1
package/lib/project-image.js
CHANGED
|
@@ -34,7 +34,7 @@ function attachImage(ctx) {
|
|
|
34
34
|
return hydrated;
|
|
35
35
|
}
|
|
36
36
|
if (!entry.imageRefs) return entry;
|
|
37
|
-
if (entry.type !== "user_message" && entry.type !== "mention_user" && entry.type !== "user_mention") return entry;
|
|
37
|
+
if (entry.type !== "user_message" && entry.type !== "mention_user" && entry.type !== "user_mention" && entry.type !== "generated_image") return entry;
|
|
38
38
|
var images = [];
|
|
39
39
|
for (var ri = 0; ri < entry.imageRefs.length; ri++) {
|
|
40
40
|
var ref = entry.imageRefs[ri];
|
package/lib/project.js
CHANGED
|
@@ -882,6 +882,7 @@ function createProjectContext(opts) {
|
|
|
882
882
|
scheduleMessage: function (session, text, resetsAt) {
|
|
883
883
|
scheduleMessage(session, text, resetsAt);
|
|
884
884
|
},
|
|
885
|
+
saveImageFile: saveImageFile,
|
|
885
886
|
getAutoContinueSetting: function (session) {
|
|
886
887
|
// Per-user setting in multi-user mode
|
|
887
888
|
if (usersModule.isMultiUser() && session && session.ownerId) {
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
.generated-image-row {
|
|
2
|
+
width: 100%;
|
|
3
|
+
max-width: var(--content-width);
|
|
4
|
+
margin: 4px auto 16px;
|
|
5
|
+
padding: 0 20px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.generated-image-card {
|
|
9
|
+
width: min(560px, 100%);
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
border: 1px solid var(--border);
|
|
12
|
+
border-radius: 14px;
|
|
13
|
+
background: var(--bg-alt);
|
|
14
|
+
box-shadow: 0 8px 28px rgba(var(--shadow-rgb), 0.16);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.generated-image-status {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 7px;
|
|
21
|
+
width: min(560px, 100%);
|
|
22
|
+
min-height: 30px;
|
|
23
|
+
padding: 0 2px 7px;
|
|
24
|
+
color: var(--text-muted);
|
|
25
|
+
font-size: 12px;
|
|
26
|
+
font-weight: 500;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.generated-image-status .lucide {
|
|
30
|
+
width: 14px;
|
|
31
|
+
height: 14px;
|
|
32
|
+
color: var(--accent);
|
|
33
|
+
animation: generated-image-sparkle 2.4s ease-in-out infinite;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.generated-image-status-dots::after {
|
|
37
|
+
content: "";
|
|
38
|
+
animation: generated-image-ellipsis 1.4s steps(4, end) infinite;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.generated-image-card--pending {
|
|
42
|
+
position: relative;
|
|
43
|
+
width: min(560px, 100%);
|
|
44
|
+
aspect-ratio: 1 / 1;
|
|
45
|
+
border-color: var(--border-subtle);
|
|
46
|
+
background: var(--bg-alt);
|
|
47
|
+
box-shadow: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.generated-image-particle-field {
|
|
51
|
+
position: absolute;
|
|
52
|
+
inset: 0;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.generated-image-particle-field::before {
|
|
57
|
+
position: absolute;
|
|
58
|
+
inset: 11%;
|
|
59
|
+
content: "";
|
|
60
|
+
background-image: radial-gradient(circle, var(--text-dimmer) 1.4px, transparent 1.7px);
|
|
61
|
+
background-position: center;
|
|
62
|
+
background-size: 25px 25px;
|
|
63
|
+
opacity: 0.52;
|
|
64
|
+
-webkit-mask-image: radial-gradient(circle, #000 0%, rgba(0, 0, 0, 0.88) 38%, transparent 72%);
|
|
65
|
+
mask-image: radial-gradient(circle, #000 0%, rgba(0, 0, 0, 0.88) 38%, transparent 72%);
|
|
66
|
+
animation: generated-image-field 3.2s ease-in-out infinite;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.generated-image-particle-field::after {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: 50%;
|
|
72
|
+
left: 50%;
|
|
73
|
+
width: 36%;
|
|
74
|
+
aspect-ratio: 1 / 1;
|
|
75
|
+
border: 1px solid var(--accent-15);
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
content: "";
|
|
78
|
+
background: radial-gradient(circle, var(--accent-12), transparent 68%);
|
|
79
|
+
transform: translate(-50%, -50%);
|
|
80
|
+
animation: generated-image-pulse 3.2s ease-in-out infinite;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@keyframes generated-image-field {
|
|
84
|
+
0%, 100% { opacity: 0.28; transform: scale(0.92) rotate(-2deg); }
|
|
85
|
+
50% { opacity: 0.68; transform: scale(1.04) rotate(2deg); }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes generated-image-pulse {
|
|
89
|
+
0%, 100% { opacity: 0.2; transform: translate(-50%, -50%) scale(0.72); }
|
|
90
|
+
50% { opacity: 0.72; transform: translate(-50%, -50%) scale(1.08); }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@keyframes generated-image-sparkle {
|
|
94
|
+
0%, 100% { opacity: 0.5; transform: scale(0.86) rotate(-8deg); }
|
|
95
|
+
50% { opacity: 1; transform: scale(1.08) rotate(8deg); }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@keyframes generated-image-ellipsis {
|
|
99
|
+
0% { content: ""; }
|
|
100
|
+
25% { content: "."; }
|
|
101
|
+
50% { content: ".."; }
|
|
102
|
+
75%, 100% { content: "..."; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media (prefers-reduced-motion: reduce) {
|
|
106
|
+
.generated-image-status .lucide,
|
|
107
|
+
.generated-image-status-dots::after,
|
|
108
|
+
.generated-image-particle-field::before,
|
|
109
|
+
.generated-image-particle-field::after {
|
|
110
|
+
animation: none;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.generated-image-preview {
|
|
115
|
+
display: grid;
|
|
116
|
+
place-items: center;
|
|
117
|
+
min-height: 180px;
|
|
118
|
+
max-height: 680px;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
background:
|
|
121
|
+
linear-gradient(45deg, rgba(var(--overlay-rgb), 0.025) 25%, transparent 25%),
|
|
122
|
+
linear-gradient(-45deg, rgba(var(--overlay-rgb), 0.025) 25%, transparent 25%),
|
|
123
|
+
linear-gradient(45deg, transparent 75%, rgba(var(--overlay-rgb), 0.025) 75%),
|
|
124
|
+
linear-gradient(-45deg, transparent 75%, rgba(var(--overlay-rgb), 0.025) 75%);
|
|
125
|
+
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
|
126
|
+
background-size: 16px 16px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.generated-image-preview img {
|
|
130
|
+
display: block;
|
|
131
|
+
width: 100%;
|
|
132
|
+
max-height: 680px;
|
|
133
|
+
object-fit: contain;
|
|
134
|
+
cursor: zoom-in;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.generated-image-footer {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
gap: 14px;
|
|
142
|
+
min-height: 52px;
|
|
143
|
+
padding: 9px 10px 9px 14px;
|
|
144
|
+
border-top: 1px solid var(--border-subtle);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.generated-image-meta {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex: 1;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 2px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.generated-image-label {
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
gap: 6px;
|
|
159
|
+
color: var(--text-secondary);
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.generated-image-label .lucide {
|
|
165
|
+
width: 13px;
|
|
166
|
+
height: 13px;
|
|
167
|
+
color: var(--accent);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.generated-image-prompt {
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
color: var(--text-muted);
|
|
173
|
+
font-size: 11px;
|
|
174
|
+
line-height: 1.35;
|
|
175
|
+
text-overflow: ellipsis;
|
|
176
|
+
white-space: nowrap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.generated-image-actions {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-shrink: 0;
|
|
182
|
+
gap: 4px;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.generated-image-action {
|
|
186
|
+
display: inline-flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
gap: 5px;
|
|
189
|
+
height: 32px;
|
|
190
|
+
padding: 0 9px;
|
|
191
|
+
border: 1px solid transparent;
|
|
192
|
+
border-radius: 8px;
|
|
193
|
+
background: transparent;
|
|
194
|
+
color: var(--text-muted);
|
|
195
|
+
font: inherit;
|
|
196
|
+
font-size: 11px;
|
|
197
|
+
text-decoration: none;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.generated-image-action:hover {
|
|
202
|
+
border-color: var(--border);
|
|
203
|
+
background: rgba(var(--overlay-rgb), 0.04);
|
|
204
|
+
color: var(--text);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.generated-image-action .lucide {
|
|
208
|
+
width: 14px;
|
|
209
|
+
height: 14px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@media (max-width: 560px) {
|
|
213
|
+
.generated-image-row { padding: 0 12px; }
|
|
214
|
+
.generated-image-card { border-radius: 12px; }
|
|
215
|
+
|
|
216
|
+
.generated-image-action span {
|
|
217
|
+
display: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.generated-image-action {
|
|
221
|
+
width: 32px;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
padding: 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
package/lib/public/css/input.css
CHANGED
|
@@ -762,7 +762,7 @@
|
|
|
762
762
|
color: var(--text-muted);
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
/* --- Bottom toolbar (attach left,
|
|
765
|
+
/* --- Bottom toolbar (attach left, session config + send right) --- */
|
|
766
766
|
#input-bottom {
|
|
767
767
|
display: flex;
|
|
768
768
|
align-items: center;
|
|
@@ -777,21 +777,7 @@
|
|
|
777
777
|
flex-shrink: 0;
|
|
778
778
|
}
|
|
779
779
|
|
|
780
|
-
/*
|
|
781
|
-
#vendor-toggle-wrap {
|
|
782
|
-
display: flex;
|
|
783
|
-
align-items: center;
|
|
784
|
-
border: 1px solid var(--border);
|
|
785
|
-
border-radius: 8px;
|
|
786
|
-
overflow: hidden;
|
|
787
|
-
height: 30px;
|
|
788
|
-
flex-shrink: 0;
|
|
789
|
-
}
|
|
790
|
-
#vendor-toggle-wrap.hidden { display: none; }
|
|
791
|
-
|
|
792
|
-
/* Compact session-vendor icon shown next to the config-chip when the
|
|
793
|
-
full vendor-toggle is hidden (because the session's vendor is already
|
|
794
|
-
committed). Visual only - not clickable. */
|
|
780
|
+
/* Compact committed-session vendor icon next to the config chip. */
|
|
795
781
|
#active-vendor-indicator {
|
|
796
782
|
display: inline-flex;
|
|
797
783
|
align-items: center;
|
|
@@ -806,73 +792,13 @@
|
|
|
806
792
|
border-radius: 4px;
|
|
807
793
|
object-fit: cover;
|
|
808
794
|
}
|
|
809
|
-
#vendor-toggle-wrap.locked {
|
|
810
|
-
border: none;
|
|
811
|
-
height: auto;
|
|
812
|
-
pointer-events: none;
|
|
813
|
-
}
|
|
814
|
-
#vendor-toggle-wrap.locked .vendor-toggle-btn:not(.active) { display: none; }
|
|
815
|
-
#vendor-toggle-wrap.locked .vendor-toggle-btn.active {
|
|
816
|
-
background: transparent;
|
|
817
|
-
padding: 0;
|
|
818
|
-
gap: 0;
|
|
819
|
-
opacity: 0.7;
|
|
820
|
-
}
|
|
821
|
-
#vendor-toggle-wrap.locked .vendor-toggle-btn.active .vendor-toggle-label { display: none; }
|
|
822
|
-
#vendor-toggle-wrap.locked .vendor-toggle-btn:first-child { border-right: none; }
|
|
823
|
-
.vendor-toggle-btn {
|
|
824
|
-
display: flex;
|
|
825
|
-
align-items: center;
|
|
826
|
-
gap: 4px;
|
|
827
|
-
padding: 0 10px;
|
|
828
|
-
height: 100%;
|
|
829
|
-
border: none;
|
|
830
|
-
background: transparent;
|
|
831
|
-
cursor: pointer;
|
|
832
|
-
font-size: 12px;
|
|
833
|
-
color: var(--text-secondary);
|
|
834
|
-
transition: background 0.15s, color 0.15s;
|
|
835
|
-
white-space: nowrap;
|
|
836
|
-
}
|
|
837
|
-
.vendor-toggle-btn:first-child { border-right: 1px solid var(--border); }
|
|
838
|
-
.vendor-toggle-btn.active {
|
|
839
|
-
background: var(--accent-12);
|
|
840
|
-
color: var(--accent);
|
|
841
|
-
font-weight: 600;
|
|
842
|
-
}
|
|
843
|
-
.vendor-toggle-btn.disabled {
|
|
844
|
-
opacity: 0.35;
|
|
845
|
-
cursor: default;
|
|
846
|
-
}
|
|
847
|
-
.vendor-toggle-btn.disabled:not(.active) { display: none; }
|
|
848
|
-
.vendor-toggle-icon {
|
|
849
|
-
width: 16px;
|
|
850
|
-
height: 16px;
|
|
851
|
-
border-radius: 4px;
|
|
852
|
-
object-fit: cover;
|
|
853
|
-
}
|
|
854
|
-
.vendor-toggle-label {
|
|
855
|
-
pointer-events: none;
|
|
856
|
-
}
|
|
857
|
-
.vendor-toggle-btn.experimental::after,
|
|
858
795
|
.vendor-experimental-badge {
|
|
859
|
-
content: "🧪";
|
|
860
796
|
font-size: 10px;
|
|
861
797
|
line-height: 1;
|
|
862
798
|
font-weight: 400;
|
|
863
799
|
filter: saturate(0.85);
|
|
864
|
-
}
|
|
865
|
-
.vendor-experimental-badge {
|
|
866
800
|
margin-left: auto;
|
|
867
801
|
}
|
|
868
|
-
@media (min-width: 901px) {
|
|
869
|
-
.vendor-toggle-btn:not(.active) .vendor-toggle-label { display: none; }
|
|
870
|
-
.vendor-toggle-btn:not(.active) { padding: 0 8px; }
|
|
871
|
-
}
|
|
872
|
-
@media (max-width: 900px) {
|
|
873
|
-
.vendor-toggle-label { display: none; }
|
|
874
|
-
.vendor-toggle-btn { padding: 0 8px; }
|
|
875
|
-
}
|
|
876
802
|
|
|
877
803
|
/* --- Attach buttons & menu --- */
|
|
878
804
|
#attach-wrap {
|
|
@@ -1290,7 +1216,6 @@
|
|
|
1290
1216
|
Attach (files/images -> path injection) and voice input stay available. */
|
|
1291
1217
|
body.tui-composer-active #schedule-btn,
|
|
1292
1218
|
body.tui-composer-active #ask-mate-btn,
|
|
1293
|
-
body.tui-composer-active #vendor-toggle-wrap,
|
|
1294
1219
|
body.tui-composer-active #active-vendor-indicator,
|
|
1295
1220
|
body.tui-composer-active #config-chip-wrap {
|
|
1296
1221
|
display: none !important;
|
package/lib/public/index.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
31
31
|
</script>
|
|
32
32
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
33
|
-
<link rel="stylesheet" href="style.css?v=
|
|
33
|
+
<link rel="stylesheet" href="style.css?v=20260829-imagegen3">
|
|
34
34
|
<style>
|
|
35
35
|
@media(max-width:768px){
|
|
36
36
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -524,48 +524,6 @@
|
|
|
524
524
|
</div>
|
|
525
525
|
</div>
|
|
526
526
|
<div id="input-bottom-right">
|
|
527
|
-
<div id="vendor-toggle-wrap">
|
|
528
|
-
<button id="vendor-btn-claude" class="vendor-toggle-btn active" data-vendor="claude">
|
|
529
|
-
<img src="/claude-code-avatar.png" class="vendor-toggle-icon" alt="Claude">
|
|
530
|
-
<span class="vendor-toggle-label">Claude Code</span>
|
|
531
|
-
</button>
|
|
532
|
-
<button id="vendor-btn-codex" class="vendor-toggle-btn" data-vendor="codex">
|
|
533
|
-
<img src="/codex-avatar.png" class="vendor-toggle-icon" alt="Codex">
|
|
534
|
-
<span class="vendor-toggle-label">Codex</span>
|
|
535
|
-
</button>
|
|
536
|
-
<button id="vendor-btn-antigravity" class="vendor-toggle-btn" data-vendor="antigravity">
|
|
537
|
-
<img src="/antigravity-avatar.png" class="vendor-toggle-icon" alt="Antigravity">
|
|
538
|
-
<span class="vendor-toggle-label">Antigravity CLI</span>
|
|
539
|
-
</button>
|
|
540
|
-
<button id="vendor-btn-opencode" class="vendor-toggle-btn" data-vendor="opencode">
|
|
541
|
-
<img src="/opencode-avatar.svg" class="vendor-toggle-icon" alt="OpenCode">
|
|
542
|
-
<span class="vendor-toggle-label">OpenCode</span>
|
|
543
|
-
</button>
|
|
544
|
-
<button id="vendor-btn-kimi" class="vendor-toggle-btn" data-vendor="kimi">
|
|
545
|
-
<img src="/kimi-avatar.svg" class="vendor-toggle-icon" alt="Kimi">
|
|
546
|
-
<span class="vendor-toggle-label">Kimi Code</span>
|
|
547
|
-
</button>
|
|
548
|
-
<button id="vendor-btn-grok" class="vendor-toggle-btn" data-vendor="grok">
|
|
549
|
-
<img src="/grok-avatar.svg" class="vendor-toggle-icon" alt="Grok">
|
|
550
|
-
<span class="vendor-toggle-label">Grok Build</span>
|
|
551
|
-
</button>
|
|
552
|
-
<button id="vendor-btn-copilot" class="vendor-toggle-btn" data-vendor="copilot">
|
|
553
|
-
<img src="/copilot-avatar.svg" class="vendor-toggle-icon" alt="GitHub Copilot">
|
|
554
|
-
<span class="vendor-toggle-label">GitHub Copilot CLI</span>
|
|
555
|
-
</button>
|
|
556
|
-
<button id="vendor-btn-qwen" class="vendor-toggle-btn" data-vendor="qwen">
|
|
557
|
-
<img src="/qwen-avatar.svg" class="vendor-toggle-icon" alt="Qwen">
|
|
558
|
-
<span class="vendor-toggle-label">Qwen Code</span>
|
|
559
|
-
</button>
|
|
560
|
-
<button id="vendor-btn-junie" class="vendor-toggle-btn" data-vendor="junie">
|
|
561
|
-
<img src="/junie-avatar.svg" class="vendor-toggle-icon" alt="Junie">
|
|
562
|
-
<span class="vendor-toggle-label">Junie CLI</span>
|
|
563
|
-
</button>
|
|
564
|
-
<button id="vendor-btn-kiro" class="vendor-toggle-btn" data-vendor="kiro">
|
|
565
|
-
<img src="/kiro-avatar.svg" class="vendor-toggle-icon" alt="Kiro">
|
|
566
|
-
<span class="vendor-toggle-label">Kiro CLI</span>
|
|
567
|
-
</button>
|
|
568
|
-
</div>
|
|
569
527
|
<div id="active-vendor-indicator" class="hidden" title="Session vendor">
|
|
570
528
|
<img id="active-vendor-icon" alt="">
|
|
571
529
|
</div>
|
|
@@ -2323,7 +2281,7 @@
|
|
|
2323
2281
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2324
2282
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2325
2283
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2326
|
-
<script type="module" src="app.js?v=
|
|
2284
|
+
<script type="module" src="app.js?v=20260829-imagegen3"></script>
|
|
2327
2285
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2328
2286
|
<div class="pwa-modal-backdrop"></div>
|
|
2329
2287
|
<div class="pwa-modal-card">
|
|
@@ -46,6 +46,7 @@ import { showImageModal, sendExtensionCommand, handleMcpToolCallMessage } from '
|
|
|
46
46
|
import { handleMcpServersState } from './mcp-ui.js';
|
|
47
47
|
import { handleShellCommandResult } from './shell-command.js';
|
|
48
48
|
import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
|
|
49
|
+
import { renderGeneratedImage, renderImageGenerationProgress, clearImageGenerationProgress, clearAllImageGenerationProgress } from './generated-images.js';
|
|
49
50
|
|
|
50
51
|
// --- App module imports ---
|
|
51
52
|
import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, showSuggestionChips, armStickyBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
@@ -178,17 +179,6 @@ export function processMessage(msg) {
|
|
|
178
179
|
if (!store.get('sessionIsProcessing')) {
|
|
179
180
|
applyDeadSessionTodoCompaction();
|
|
180
181
|
}
|
|
181
|
-
// Show the locked vendor toggle only when history exists AND the
|
|
182
|
-
// vendor isn't already committed. With a committed vendor,
|
|
183
|
-
// session_switched has already hidden the toggle and shown the
|
|
184
|
-
// small #active-vendor-indicator; re-showing the locked toggle
|
|
185
|
-
// here would duplicate the avatar next to the indicator.
|
|
186
|
-
var _hTotal = store.get('historyTotal') || 0;
|
|
187
|
-
var _vtw2 = document.getElementById("vendor-toggle-wrap");
|
|
188
|
-
if (_vtw2 && _hTotal > 0 && !store.get('currentVendor')) {
|
|
189
|
-
_vtw2.classList.remove("hidden");
|
|
190
|
-
_vtw2.classList.add("locked");
|
|
191
|
-
}
|
|
192
182
|
// Restore cached rich context usage BEFORE updateContextPanel runs
|
|
193
183
|
if (msg.contextUsage) {
|
|
194
184
|
store.set({ richContextUsage: msg.contextUsage });
|
|
@@ -747,30 +737,9 @@ export function processMessage(msg) {
|
|
|
747
737
|
selectDefaultVendorForBlankSession();
|
|
748
738
|
}
|
|
749
739
|
if (!msg.model) requestVendorModels(store.get('currentVendor') || "claude", true);
|
|
750
|
-
//
|
|
751
|
-
// model chip.
|
|
752
|
-
// - Session has an explicit vendor: hide the toggle (it's
|
|
753
|
-
// committed for this conversation) and show a small avatar
|
|
754
|
-
// next to the config chip so the user still sees which vendor
|
|
755
|
-
// is in use.
|
|
756
|
-
// - History without recorded vendor: show locked toggle, no icon
|
|
757
|
-
// (we don't know what to render).
|
|
758
|
-
// - Brand-new no-vendor session: show toggle, no icon.
|
|
759
|
-
var _vtw = document.getElementById("vendor-toggle-wrap");
|
|
740
|
+
// Show the committed session vendor next to the model chip.
|
|
760
741
|
var _avi = document.getElementById("active-vendor-indicator");
|
|
761
742
|
var _avIcon = document.getElementById("active-vendor-icon");
|
|
762
|
-
if (_vtw) {
|
|
763
|
-
if (msg.vendor) {
|
|
764
|
-
_vtw.classList.add("hidden");
|
|
765
|
-
_vtw.classList.remove("locked");
|
|
766
|
-
} else if (msg.hasHistory) {
|
|
767
|
-
_vtw.classList.remove("hidden");
|
|
768
|
-
_vtw.classList.add("locked");
|
|
769
|
-
} else {
|
|
770
|
-
_vtw.classList.remove("locked");
|
|
771
|
-
_vtw.classList.remove("hidden");
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
743
|
if (_avi && _avIcon) {
|
|
775
744
|
if (msg.vendor && VENDOR_AVATARS[msg.vendor]) {
|
|
776
745
|
_avIcon.src = VENDOR_AVATARS[msg.vendor];
|
|
@@ -996,6 +965,7 @@ export function processMessage(msg) {
|
|
|
996
965
|
break;
|
|
997
966
|
|
|
998
967
|
case "tool_executing":
|
|
968
|
+
if (msg.name === "ImageGen") renderImageGenerationProgress(msg);
|
|
999
969
|
if ((msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) && msg.input) {
|
|
1000
970
|
var _dpTool = getTools()[msg.id];
|
|
1001
971
|
if (_dpTool) {
|
|
@@ -1047,6 +1017,7 @@ export function processMessage(msg) {
|
|
|
1047
1017
|
case "tool_result": {
|
|
1048
1018
|
var tr = getTools()[msg.id];
|
|
1049
1019
|
if (tr && tr.hidden) break; // skip hidden plan tools
|
|
1020
|
+
if (msg.is_error && tr && tr.name === "ImageGen") clearImageGenerationProgress(msg.id);
|
|
1050
1021
|
// Always call updateToolResult for Edit (to show diff from input), or when content exists
|
|
1051
1022
|
if (msg.content != null || msg.images || (tr && tr.name === "Edit" && tr.input && tr.input.old_string)) {
|
|
1052
1023
|
updateToolResult(msg.id, msg.content || "", msg.is_error || false, msg.images);
|
|
@@ -1061,6 +1032,10 @@ export function processMessage(msg) {
|
|
|
1061
1032
|
}
|
|
1062
1033
|
break;
|
|
1063
1034
|
|
|
1035
|
+
case "generated_image":
|
|
1036
|
+
renderGeneratedImage(msg);
|
|
1037
|
+
break;
|
|
1038
|
+
|
|
1064
1039
|
case "ask_user_answered":
|
|
1065
1040
|
markAskUserAnswered(msg.toolId, msg.answers);
|
|
1066
1041
|
stopUrgentBlink();
|
|
@@ -1160,6 +1135,7 @@ export function processMessage(msg) {
|
|
|
1160
1135
|
stopThinking();
|
|
1161
1136
|
markAllToolsDone();
|
|
1162
1137
|
closeToolGroup();
|
|
1138
|
+
clearAllImageGenerationProgress();
|
|
1163
1139
|
finalizeAssistantBlock();
|
|
1164
1140
|
addTurnMeta(msg.cost, msg.duration);
|
|
1165
1141
|
accumulateUsage(msg.cost, msg.usage);
|
|
@@ -5,7 +5,6 @@ import { refreshIcons } from "./icons.js";
|
|
|
5
5
|
import { escapeHtml, showToast } from "./utils.js";
|
|
6
6
|
import { store } from './store.js';
|
|
7
7
|
import { getWs } from './ws-ref.js';
|
|
8
|
-
import { VENDOR_NAMES, isExperimentalVendor } from './app-rendering.js';
|
|
9
8
|
import { reportPaneContext } from './pane-bridge.js';
|
|
10
9
|
import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelPickerOpen, modelDisplayName } from './model-picker.js';
|
|
11
10
|
|
|
@@ -368,79 +367,8 @@ export function initPanels() {
|
|
|
368
367
|
configWebsearchSection = $("config-websearch-section");
|
|
369
368
|
configWebsearchBar = $("config-websearch-bar");
|
|
370
369
|
|
|
371
|
-
// --- Vendor toggle ---
|
|
372
|
-
var vendorToggleWrap = $("vendor-toggle-wrap");
|
|
373
|
-
var vendorBtnClaude = $("vendor-btn-claude");
|
|
374
|
-
var vendorBtnCodex = $("vendor-btn-codex");
|
|
375
|
-
var vendorBtnAntigravity = $("vendor-btn-antigravity");
|
|
376
|
-
var vendorBtnOpenCode = $("vendor-btn-opencode");
|
|
377
|
-
var vendorBtnKimi = $("vendor-btn-kimi");
|
|
378
|
-
var vendorBtnGrok = $("vendor-btn-grok");
|
|
379
|
-
var vendorBtnCopilot = $("vendor-btn-copilot");
|
|
380
|
-
var vendorBtnQwen = $("vendor-btn-qwen");
|
|
381
|
-
var vendorBtnJunie = $("vendor-btn-junie");
|
|
382
|
-
var vendorBtnKiro = $("vendor-btn-kiro");
|
|
383
|
-
var vendorBtns = {
|
|
384
|
-
claude: vendorBtnClaude,
|
|
385
|
-
codex: vendorBtnCodex,
|
|
386
|
-
antigravity: vendorBtnAntigravity,
|
|
387
|
-
opencode: vendorBtnOpenCode,
|
|
388
|
-
kimi: vendorBtnKimi,
|
|
389
|
-
grok: vendorBtnGrok,
|
|
390
|
-
copilot: vendorBtnCopilot,
|
|
391
|
-
qwen: vendorBtnQwen,
|
|
392
|
-
junie: vendorBtnJunie,
|
|
393
|
-
kiro: vendorBtnKiro,
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
function updateVendorToggle() {
|
|
397
|
-
var installed = store.get('installedVendors') || [];
|
|
398
|
-
var current = store.get('currentVendor') || "claude";
|
|
399
|
-
|
|
400
|
-
var vendors = Object.keys(vendorBtns);
|
|
401
|
-
for (var i = 0; i < vendors.length; i++) {
|
|
402
|
-
var v = vendors[i];
|
|
403
|
-
var btn = vendorBtns[v];
|
|
404
|
-
if (!btn) continue;
|
|
405
|
-
var isInstalled = installed.indexOf(v) !== -1;
|
|
406
|
-
btn.classList.toggle("active", v === current);
|
|
407
|
-
btn.classList.toggle("disabled", !isInstalled);
|
|
408
|
-
btn.classList.toggle("experimental", isExperimentalVendor(v));
|
|
409
|
-
var title = isInstalled ? (VENDOR_NAMES[v] || v) : (VENDOR_NAMES[v] || v) + " is not installed";
|
|
410
|
-
if (isExperimentalVendor(v)) title += " · Experimental integration; not yet validated through direct use testing";
|
|
411
|
-
btn.title = title;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function onVendorClick(vendor) {
|
|
416
|
-
if (vendor === (store.get('currentVendor') || "claude")) return;
|
|
417
|
-
var installed = store.get('installedVendors') || [];
|
|
418
|
-
if (installed.indexOf(vendor) === -1) return;
|
|
419
|
-
store.set({ currentVendor: vendor, currentModel: "", currentModels: [], vendorCapabilities: {}, vendorSelectionLocked: true, sessionVendorBound: true });
|
|
420
|
-
var ws = getWs();
|
|
421
|
-
if (ws) ws.send(JSON.stringify({ type: "set_vendor", vendor: vendor }));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (vendorBtnClaude) vendorBtnClaude.addEventListener("click", function() { onVendorClick("claude"); });
|
|
425
|
-
if (vendorBtnCodex) vendorBtnCodex.addEventListener("click", function() { onVendorClick("codex"); });
|
|
426
|
-
if (vendorBtnAntigravity) vendorBtnAntigravity.addEventListener("click", function() { onVendorClick("antigravity"); });
|
|
427
|
-
if (vendorBtnOpenCode) vendorBtnOpenCode.addEventListener("click", function() { onVendorClick("opencode"); });
|
|
428
|
-
if (vendorBtnKimi) vendorBtnKimi.addEventListener("click", function() { onVendorClick("kimi"); });
|
|
429
|
-
if (vendorBtnGrok) vendorBtnGrok.addEventListener("click", function() { onVendorClick("grok"); });
|
|
430
|
-
if (vendorBtnCopilot) vendorBtnCopilot.addEventListener("click", function() { onVendorClick("copilot"); });
|
|
431
|
-
if (vendorBtnQwen) vendorBtnQwen.addEventListener("click", function() { onVendorClick("qwen"); });
|
|
432
|
-
if (vendorBtnJunie) vendorBtnJunie.addEventListener("click", function() { onVendorClick("junie"); });
|
|
433
|
-
if (vendorBtnKiro) vendorBtnKiro.addEventListener("click", function() { onVendorClick("kiro"); });
|
|
434
|
-
|
|
435
370
|
// --- Reactive UI sync ---
|
|
436
371
|
store.subscribe(function (state, prev) {
|
|
437
|
-
// Vendor toggle state
|
|
438
|
-
if (state.availableVendors !== prev.availableVendors ||
|
|
439
|
-
state.installedVendors !== prev.installedVendors ||
|
|
440
|
-
state.currentVendor !== prev.currentVendor) {
|
|
441
|
-
updateVendorToggle();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
372
|
// richContextUsage changed -> update popover + panel
|
|
445
373
|
if (state.richContextUsage !== prev.richContextUsage) {
|
|
446
374
|
if (state.richContextUsage) {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Inline presentation for images produced by Codex ImageGen.
|
|
2
|
+
|
|
3
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
4
|
+
import { showImageModal } from './app-misc.js';
|
|
5
|
+
import { addToMessages, scrollToBottom } from './app-rendering.js';
|
|
6
|
+
|
|
7
|
+
function fileNameFromImage(image) {
|
|
8
|
+
if (image.fileName) return image.fileName;
|
|
9
|
+
try {
|
|
10
|
+
var url = new URL(image.url, window.location.href);
|
|
11
|
+
return decodeURIComponent(url.pathname.split('/').pop()) || 'generated-image.png';
|
|
12
|
+
} catch (e) {
|
|
13
|
+
return 'generated-image.png';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function actionButton(icon, label) {
|
|
18
|
+
var button = document.createElement('button');
|
|
19
|
+
button.type = 'button';
|
|
20
|
+
button.className = 'generated-image-action';
|
|
21
|
+
button.title = label;
|
|
22
|
+
button.setAttribute('aria-label', label);
|
|
23
|
+
button.innerHTML = iconHtml(icon) + '<span>' + label + '</span>';
|
|
24
|
+
return button;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function findProgressRow(toolId) {
|
|
28
|
+
var rows = document.querySelectorAll('.generated-image-row[data-image-tool-id]');
|
|
29
|
+
for (var i = 0; i < rows.length; i++) {
|
|
30
|
+
if (rows[i].dataset.imageToolId === String(toolId || '')) return rows[i];
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function renderImageGenerationProgress(msg) {
|
|
36
|
+
if (!msg.id || findProgressRow(msg.id)) return;
|
|
37
|
+
|
|
38
|
+
var row = document.createElement('div');
|
|
39
|
+
row.className = 'generated-image-row generated-image-row--pending';
|
|
40
|
+
row.dataset.imageToolId = msg.id;
|
|
41
|
+
|
|
42
|
+
var status = document.createElement('div');
|
|
43
|
+
status.className = 'generated-image-status';
|
|
44
|
+
status.setAttribute('role', 'status');
|
|
45
|
+
status.setAttribute('aria-live', 'polite');
|
|
46
|
+
status.innerHTML = iconHtml('sparkles') + '<span>Creating image</span><span class="generated-image-status-dots" aria-hidden="true"></span>';
|
|
47
|
+
row.appendChild(status);
|
|
48
|
+
|
|
49
|
+
var card = document.createElement('div');
|
|
50
|
+
card.className = 'generated-image-card generated-image-card--pending';
|
|
51
|
+
card.setAttribute('aria-hidden', 'true');
|
|
52
|
+
var field = document.createElement('div');
|
|
53
|
+
field.className = 'generated-image-particle-field';
|
|
54
|
+
card.appendChild(field);
|
|
55
|
+
row.appendChild(card);
|
|
56
|
+
|
|
57
|
+
addToMessages(row);
|
|
58
|
+
refreshIcons(row);
|
|
59
|
+
scrollToBottom();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function clearImageGenerationProgress(toolId) {
|
|
63
|
+
var row = findProgressRow(toolId);
|
|
64
|
+
if (row && row.classList.contains('generated-image-row--pending')) row.remove();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function clearAllImageGenerationProgress() {
|
|
68
|
+
var rows = document.querySelectorAll('.generated-image-row--pending');
|
|
69
|
+
for (var i = 0; i < rows.length; i++) rows[i].remove();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function renderGeneratedImage(msg) {
|
|
73
|
+
var image = msg.images && msg.images[0];
|
|
74
|
+
if (!image || !image.url) return;
|
|
75
|
+
|
|
76
|
+
var row = document.createElement('div');
|
|
77
|
+
row.className = 'generated-image-row';
|
|
78
|
+
row.dataset.imageToolId = msg.id || '';
|
|
79
|
+
var card = document.createElement('figure');
|
|
80
|
+
card.className = 'generated-image-card';
|
|
81
|
+
card.dataset.toolId = msg.id || '';
|
|
82
|
+
|
|
83
|
+
var imageWrap = document.createElement('div');
|
|
84
|
+
imageWrap.className = 'generated-image-preview';
|
|
85
|
+
var img = document.createElement('img');
|
|
86
|
+
img.src = image.url;
|
|
87
|
+
img.alt = msg.prompt ? 'Generated image: ' + msg.prompt : 'Generated image';
|
|
88
|
+
img.loading = 'lazy';
|
|
89
|
+
img.addEventListener('click', function () { showImageModal(image.url); });
|
|
90
|
+
imageWrap.appendChild(img);
|
|
91
|
+
card.appendChild(imageWrap);
|
|
92
|
+
|
|
93
|
+
var footer = document.createElement('figcaption');
|
|
94
|
+
footer.className = 'generated-image-footer';
|
|
95
|
+
var meta = document.createElement('div');
|
|
96
|
+
meta.className = 'generated-image-meta';
|
|
97
|
+
var label = document.createElement('span');
|
|
98
|
+
label.className = 'generated-image-label';
|
|
99
|
+
label.innerHTML = iconHtml('sparkles') + '<span>Generated image</span>';
|
|
100
|
+
meta.appendChild(label);
|
|
101
|
+
if (msg.prompt) {
|
|
102
|
+
var prompt = document.createElement('span');
|
|
103
|
+
prompt.className = 'generated-image-prompt';
|
|
104
|
+
prompt.textContent = msg.prompt;
|
|
105
|
+
prompt.title = msg.prompt;
|
|
106
|
+
meta.appendChild(prompt);
|
|
107
|
+
}
|
|
108
|
+
footer.appendChild(meta);
|
|
109
|
+
|
|
110
|
+
var actions = document.createElement('div');
|
|
111
|
+
actions.className = 'generated-image-actions';
|
|
112
|
+
var openButton = actionButton('maximize-2', 'Open');
|
|
113
|
+
openButton.addEventListener('click', function () { showImageModal(image.url); });
|
|
114
|
+
actions.appendChild(openButton);
|
|
115
|
+
var downloadLink = document.createElement('a');
|
|
116
|
+
downloadLink.className = 'generated-image-action';
|
|
117
|
+
downloadLink.href = image.url;
|
|
118
|
+
downloadLink.download = fileNameFromImage(image);
|
|
119
|
+
downloadLink.title = 'Download';
|
|
120
|
+
downloadLink.setAttribute('aria-label', 'Download');
|
|
121
|
+
downloadLink.innerHTML = iconHtml('download') + '<span>Download</span>';
|
|
122
|
+
actions.appendChild(downloadLink);
|
|
123
|
+
footer.appendChild(actions);
|
|
124
|
+
card.appendChild(footer);
|
|
125
|
+
row.appendChild(card);
|
|
126
|
+
|
|
127
|
+
var progressRow = findProgressRow(msg.id);
|
|
128
|
+
if (progressRow) progressRow.replaceWith(row);
|
|
129
|
+
else addToMessages(row);
|
|
130
|
+
refreshIcons(row);
|
|
131
|
+
scrollToBottom();
|
|
132
|
+
}
|
|
@@ -78,23 +78,15 @@ export var builtinCommands = [
|
|
|
78
78
|
|
|
79
79
|
function commitVendorForTurn() {
|
|
80
80
|
var committedVendor = store.get('currentVendor');
|
|
81
|
-
var vendorToggle = document.getElementById("vendor-toggle-wrap");
|
|
82
81
|
var activeIndicator = document.getElementById("active-vendor-indicator");
|
|
83
82
|
var activeIcon = document.getElementById("active-vendor-icon");
|
|
84
83
|
if (committedVendor) {
|
|
85
|
-
if (vendorToggle) {
|
|
86
|
-
vendorToggle.classList.add("hidden");
|
|
87
|
-
vendorToggle.classList.remove("locked");
|
|
88
|
-
}
|
|
89
84
|
if (activeIndicator && activeIcon) {
|
|
90
85
|
activeIcon.src = VENDOR_AVATARS[committedVendor] || VENDOR_AVATARS.claude;
|
|
91
86
|
activeIcon.alt = VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude;
|
|
92
87
|
activeIndicator.title = (VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
93
88
|
activeIndicator.classList.remove("hidden");
|
|
94
89
|
}
|
|
95
|
-
} else if (vendorToggle) {
|
|
96
|
-
vendorToggle.classList.remove("hidden");
|
|
97
|
-
vendorToggle.classList.add("locked");
|
|
98
90
|
}
|
|
99
91
|
store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
|
|
100
92
|
}
|
|
@@ -339,8 +331,7 @@ export function sendMessage() {
|
|
|
339
331
|
|
|
340
332
|
// First message commits the vendor and bumps the session into
|
|
341
333
|
// has-history state. The server won't re-fire session_switched after a
|
|
342
|
-
// message, so
|
|
343
|
-
// hide the picker, show the small avatar next to the config chip.
|
|
334
|
+
// message, so show the small vendor avatar next to the config chip.
|
|
344
335
|
// Bumping sessionHasHistory drives in-session lock states (e.g. the
|
|
345
336
|
// Codex model picker that becomes informational once the thread is
|
|
346
337
|
// bound to a model).
|
package/lib/public/style.css
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
@import url("css/overlays.css?v=20260827-brand3");
|
|
9
9
|
@import url("css/menus.css");
|
|
10
10
|
@import url("css/messages.css?v=20260828-delegated-muted1");
|
|
11
|
+
@import url("css/generated-images.css?v=20260829c");
|
|
11
12
|
@import url("css/rewind.css");
|
|
12
13
|
@import url("css/input.css?v=20260828-composer-depth1");
|
|
13
14
|
@import url("css/filebrowser.css?v=20260828-workbench4");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -392,6 +392,8 @@ function createSDKBridge(opts) {
|
|
|
392
392
|
pushModule: pushModule,
|
|
393
393
|
getNotificationsModule: getNotificationsModule,
|
|
394
394
|
adapter: adapter,
|
|
395
|
+
saveImageFile: opts.saveImageFile,
|
|
396
|
+
getSessionLinuxUser: getSessionLinuxUser,
|
|
395
397
|
onProcessingChanged: onProcessingChanged,
|
|
396
398
|
onTurnDone: onTurnDone,
|
|
397
399
|
onAutoTitle: function (session) { autoGenerateTitle(session); },
|
|
@@ -18,6 +18,8 @@ function attachMessageProcessor(ctx) {
|
|
|
18
18
|
var opts = ctx.opts;
|
|
19
19
|
var discoverSkillDirs = ctx.discoverSkillDirs;
|
|
20
20
|
var mergeSkills = ctx.mergeSkills;
|
|
21
|
+
var saveImageFile = ctx.saveImageFile;
|
|
22
|
+
var getSessionLinuxUser = ctx.getSessionLinuxUser || function () { return null; };
|
|
21
23
|
|
|
22
24
|
var AUTO_TITLE_TURN_THRESHOLD = 2;
|
|
23
25
|
|
|
@@ -29,8 +31,19 @@ function attachMessageProcessor(ctx) {
|
|
|
29
31
|
return null;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function sendAndRecord(session, obj) {
|
|
33
|
-
sm.sendAndRecord(session, obj);
|
|
34
|
+
function sendAndRecord(session, obj, clientObj) {
|
|
35
|
+
sm.sendAndRecord(session, obj, clientObj);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseGeneratedImage(parsed) {
|
|
39
|
+
var data = parsed.data || "";
|
|
40
|
+
var mediaType = parsed.mediaType || "image/png";
|
|
41
|
+
var dataUrlMatch = /^data:(image\/[a-z0-9.+-]+);base64,([\s\S]+)$/i.exec(data);
|
|
42
|
+
if (dataUrlMatch) {
|
|
43
|
+
mediaType = dataUrlMatch[1];
|
|
44
|
+
data = dataUrlMatch[2];
|
|
45
|
+
}
|
|
46
|
+
return { mediaType: mediaType, data: data.replace(/\s/g, "") };
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
function sendToSession(session, obj) {
|
|
@@ -276,6 +289,48 @@ function attachMessageProcessor(ctx) {
|
|
|
276
289
|
is_error: !!parsed.isError,
|
|
277
290
|
});
|
|
278
291
|
|
|
292
|
+
} else if (parsed.yokeType === "generated_image") {
|
|
293
|
+
var generated = parseGeneratedImage(parsed);
|
|
294
|
+
var fileName = null;
|
|
295
|
+
if (!parsed.isError && generated.data && typeof saveImageFile === "function") {
|
|
296
|
+
fileName = saveImageFile(generated.mediaType, generated.data, getSessionLinuxUser(session));
|
|
297
|
+
}
|
|
298
|
+
if (!fileName) {
|
|
299
|
+
sendAndRecord(session, {
|
|
300
|
+
type: "tool_result",
|
|
301
|
+
id: parsed.toolId,
|
|
302
|
+
content: parsed.isError ? "Image generation failed." : "The generated image could not be saved.",
|
|
303
|
+
is_error: true,
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
sendAndRecord(session, {
|
|
307
|
+
type: "tool_result",
|
|
308
|
+
id: parsed.toolId,
|
|
309
|
+
content: "Image generated",
|
|
310
|
+
is_error: false,
|
|
311
|
+
});
|
|
312
|
+
var imageRef = { mediaType: generated.mediaType, file: fileName };
|
|
313
|
+
var storedImage = {
|
|
314
|
+
type: "generated_image",
|
|
315
|
+
id: parsed.toolId,
|
|
316
|
+
prompt: parsed.prompt || "",
|
|
317
|
+
transparentBackground: parsed.transparentBackground,
|
|
318
|
+
imageRefs: [imageRef],
|
|
319
|
+
};
|
|
320
|
+
var liveImage = {
|
|
321
|
+
type: "generated_image",
|
|
322
|
+
id: parsed.toolId,
|
|
323
|
+
prompt: parsed.prompt || "",
|
|
324
|
+
transparentBackground: parsed.transparentBackground,
|
|
325
|
+
images: [{
|
|
326
|
+
mediaType: generated.mediaType,
|
|
327
|
+
url: "/p/" + slug + "/images/" + fileName,
|
|
328
|
+
fileName: fileName,
|
|
329
|
+
}],
|
|
330
|
+
};
|
|
331
|
+
sendAndRecord(session, storedImage, liveImage);
|
|
332
|
+
}
|
|
333
|
+
|
|
279
334
|
} else if (parsed.yokeType === "plan_updated") {
|
|
280
335
|
var todos = Array.isArray(parsed.plan) ? parsed.plan.map(function(step, idx) {
|
|
281
336
|
var todo = {
|
package/lib/sessions.js
CHANGED
|
@@ -874,9 +874,11 @@ function createSessionManager(opts) {
|
|
|
874
874
|
}
|
|
875
875
|
}
|
|
876
876
|
|
|
877
|
-
function doSendAndRecord(session, obj) {
|
|
877
|
+
function doSendAndRecord(session, obj, clientObj) {
|
|
878
878
|
// Stamp every recorded message so history replay preserves original times
|
|
879
879
|
if (!obj._ts) obj._ts = Date.now();
|
|
880
|
+
var outgoingObj = clientObj || obj;
|
|
881
|
+
if (!outgoingObj._ts) outgoingObj._ts = obj._ts;
|
|
880
882
|
session.history.push(obj);
|
|
881
883
|
appendToSessionFile(session, obj);
|
|
882
884
|
// Per-session out-of-band subscribers (used by home-chat to mirror
|
|
@@ -885,12 +887,12 @@ function createSessionManager(opts) {
|
|
|
885
887
|
// clients; they are responsible for any transform + dispatch.
|
|
886
888
|
if (session._subscribers && session._subscribers.size > 0) {
|
|
887
889
|
for (var sub of session._subscribers) {
|
|
888
|
-
try { sub(
|
|
890
|
+
try { sub(outgoingObj); } catch (e) { /* swallow — subscriber is optional */ }
|
|
889
891
|
}
|
|
890
892
|
}
|
|
891
893
|
if (sendEach) {
|
|
892
894
|
// Multi-user: send to clients whose active session matches this one
|
|
893
|
-
var data = JSON.stringify(
|
|
895
|
+
var data = JSON.stringify(outgoingObj);
|
|
894
896
|
var ioData = null;
|
|
895
897
|
sendEach(function (ws) {
|
|
896
898
|
if (ws._clayActiveSession === session.localId) {
|
|
@@ -901,7 +903,7 @@ function createSessionManager(opts) {
|
|
|
901
903
|
}
|
|
902
904
|
// Track unread: increment on "done" for clients not viewing this session
|
|
903
905
|
// Only count if session has no owner (my session) or owner matches this client
|
|
904
|
-
if (
|
|
906
|
+
if (outgoingObj.type === "done" && ws._clayActiveSession !== session.localId) {
|
|
905
907
|
var _isMySession = !session.ownerId || (ws._clayUser && ws._clayUser.id === session.ownerId);
|
|
906
908
|
if (_isMySession) {
|
|
907
909
|
if (!ws._clayUnread) ws._clayUnread = {};
|
|
@@ -917,10 +919,10 @@ function createSessionManager(opts) {
|
|
|
917
919
|
setTimeout(function () { session._ioThrottle = false; }, 80);
|
|
918
920
|
}
|
|
919
921
|
} else if (session.localId === activeSessionId) {
|
|
920
|
-
send(
|
|
922
|
+
send(outgoingObj);
|
|
921
923
|
} else {
|
|
922
924
|
// Track unread for single-user mode on "done"
|
|
923
|
-
if (
|
|
925
|
+
if (outgoingObj.type === "done") {
|
|
924
926
|
singleUserUnread[session.localId] = (singleUserUnread[session.localId] || 0) + 1;
|
|
925
927
|
send({ type: "session_unread", id: session.localId, count: singleUserUnread[session.localId] });
|
|
926
928
|
}
|
|
@@ -31,6 +31,7 @@ var backgroundTasks = require("../codex-background-tasks");
|
|
|
31
31
|
// agentMessage -> text response
|
|
32
32
|
// reasoning -> thinking
|
|
33
33
|
// commandExecution -> bash/shell
|
|
34
|
+
// imageGeneration -> ImageGen with a persisted inline image
|
|
34
35
|
// fileChange -> file edits
|
|
35
36
|
// mcpToolCall -> MCP tool
|
|
36
37
|
// webSearch -> web search
|
|
@@ -417,6 +418,44 @@ function flattenEvent(notification, state) {
|
|
|
417
418
|
return events;
|
|
418
419
|
}
|
|
419
420
|
|
|
421
|
+
// Image generation. Codex emits the generated PNG as base64 in `result`;
|
|
422
|
+
// the message processor persists it before anything reaches the browser.
|
|
423
|
+
if (item.type === "imageGeneration" || item.type === "image_generation") {
|
|
424
|
+
if (!state.toolBlocks[item.id]) {
|
|
425
|
+
state.blockCounter++;
|
|
426
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
427
|
+
var imageBlockId = state.toolBlocks[item.id];
|
|
428
|
+
events.push({
|
|
429
|
+
yokeType: "tool_start",
|
|
430
|
+
blockId: imageBlockId,
|
|
431
|
+
toolId: item.id,
|
|
432
|
+
toolName: "ImageGen",
|
|
433
|
+
});
|
|
434
|
+
events.push({
|
|
435
|
+
yokeType: "tool_executing",
|
|
436
|
+
blockId: imageBlockId,
|
|
437
|
+
toolId: item.id,
|
|
438
|
+
toolName: "ImageGen",
|
|
439
|
+
input: { prompt: item.revisedPrompt || "" },
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (evtPhase === "completed") {
|
|
443
|
+
events.push({
|
|
444
|
+
yokeType: "generated_image",
|
|
445
|
+
toolId: item.id,
|
|
446
|
+
blockId: state.toolBlocks[item.id],
|
|
447
|
+
data: item.result || "",
|
|
448
|
+
mediaType: item.mediaType || "image/png",
|
|
449
|
+
prompt: item.revisedPrompt || "",
|
|
450
|
+
savedPath: item.savedPath || null,
|
|
451
|
+
transparentBackground: item.transparentBackground == null ? null : !!item.transparentBackground,
|
|
452
|
+
isError: item.status === "failed",
|
|
453
|
+
status: item.status || "completed",
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return events;
|
|
457
|
+
}
|
|
458
|
+
|
|
420
459
|
// Command execution (bash/shell)
|
|
421
460
|
if (item.type === "commandExecution" || item.type === "command_execution") {
|
|
422
461
|
var commandText = item.command || state.commandInputs[item.id] || "";
|
package/package.json
CHANGED