@plannotator/ui 0.49.0 → 0.51.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.
- package/HANDOFF.md +202 -1
- package/LICENSE +191 -0
- package/README.md +23 -0
- package/components/AISettingsTab.tsx +33 -1
- package/components/AgentToolAnnouncementDialog.tsx +347 -0
- package/components/AgentsTab.tsx +9 -9
- package/components/AskSessionAnnouncementDialog.tsx +329 -0
- package/components/CommentPopover.tsx +32 -18
- package/components/DecisionControl.tsx +2 -7
- package/components/DiagramBlock.tsx +46 -4
- package/components/DiffFileTree.tsx +363 -0
- package/components/DocBadges.tsx +12 -8
- package/components/ImageLightbox.tsx +66 -0
- package/components/PlanHeaderMenu.tsx +1 -1
- package/components/ProviderIcons.tsx +15 -3
- package/components/Settings.tsx +73 -1
- package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
- package/components/Viewer.tsx +5 -28
- package/components/ai/AIProviderBar.tsx +20 -3
- package/components/ai/DocumentAIChatPanel.tsx +23 -3
- package/components/ai/SessionAskNotice.tsx +101 -0
- package/components/blocks/QuestionBlock.tsx +24 -19
- package/components/blocks/QuestionContext.tsx +81 -0
- package/components/diagram/DiagramComposer.tsx +54 -3
- package/components/diagram/DiagramViewer.tsx +31 -2
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +13 -3
- package/components/html-viewer/bridge-script.asset.js +209 -16
- package/components/html-viewer/bridge-script.ts +209 -16
- package/components/html-viewer/useHtmlAnnotation.ts +36 -55
- package/configure.ts +12 -0
- package/hooks/useAIChat.ts +96 -10
- package/hooks/useAIProviderActivation.ts +13 -0
- package/hooks/useAIProviderConfig.ts +32 -14
- package/hooks/useActiveSection.ts +3 -2
- package/hooks/useAgentToolSetting.ts +33 -0
- package/hooks/useAnnotationDraft.ts +53 -7
- package/hooks/useAnnotationHighlighter.ts +101 -5
- package/hooks/useDiffFileTreeExpansion.ts +92 -0
- package/hooks/useExternalAnnotations.ts +21 -1
- package/hooks/useFirstRunAnnouncementWindow.ts +114 -0
- package/hooks/useLatchedTrue.ts +15 -0
- package/hooks/useLinkedDoc.ts +64 -4
- package/hooks/useModelCatalogs.ts +9 -2
- package/package.json +3 -2
- package/styles.css +1 -1
- package/themes/gruvbox.css +6 -0
- package/types.ts +17 -0
- package/utils/agentClosed.ts +33 -0
- package/utils/agentToolAnnouncement.ts +109 -0
- package/utils/agentToolSetting.ts +108 -0
- package/utils/aiPrompt.ts +31 -0
- package/utils/aiProvider.ts +41 -0
- package/utils/annotationOwnsHighlight.ts +27 -0
- package/utils/annotationScope.ts +15 -0
- package/utils/askSessionAnnouncement.ts +107 -0
- package/utils/blockTargeting.ts +61 -2
- package/utils/diagramAskAI.ts +161 -0
- package/utils/diffFileTree.ts +203 -0
- package/utils/htmlLinkNavigation.ts +87 -3
- package/utils/parser.ts +236 -110
- package/utils/questionAnswers.ts +3 -1
- package/utils/quickLabels.ts +2 -2
- package/utils/syntaxTheme.ts +33 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { agentToolHostName } from '../utils/agentToolSetting';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One-time offer to turn on the `plannotator` agent tool on Pi and OpenCode 2,
|
|
7
|
+
* where it is off by default. Question first ("Do you use Plannotator as a
|
|
8
|
+
* skill?"), two example requests, one line of why, the cost, and two buttons,
|
|
9
|
+
* under a small diagram of the round trip (the agent opens a review right
|
|
10
|
+
* away, keeps working, your feedback comes back). Same shell as the other
|
|
11
|
+
* first-run announcements (portal, z-[100], hand-rolled Escape + Tab wrap +
|
|
12
|
+
* focus restore, backdrop dismiss, capture-phase keydown that swallows
|
|
13
|
+
* Mod+Enter so a keystroke aimed here cannot approve a plan or post a review
|
|
14
|
+
* behind it).
|
|
15
|
+
*
|
|
16
|
+
* Unlike those, it collects a decision, so it never dismisses itself while a
|
|
17
|
+
* save is in flight, and it reports the outcome in place: a failed save shows
|
|
18
|
+
* the server's reason and keeps both buttons; a successful one says when the
|
|
19
|
+
* tool arrives and offers Done.
|
|
20
|
+
*
|
|
21
|
+
* LAST in each app's first-run dialog chain; the Apps gate rendering through
|
|
22
|
+
* agentToolAnnouncementEligible and useFirstRunAnnouncementWindow.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type AgentToolOfferHost = 'pi' | 'opencode';
|
|
26
|
+
|
|
27
|
+
interface AgentToolAnnouncementDialogProps {
|
|
28
|
+
readonly isOpen: boolean;
|
|
29
|
+
/** The session's host; names the agent and when the change applies. */
|
|
30
|
+
readonly host: AgentToolOfferHost;
|
|
31
|
+
/**
|
|
32
|
+
* "Yes, turn it on": writes the setting (POST /api/config `{ agentTool: true }`).
|
|
33
|
+
* Rejects with an Error whose message is shown. The App marks the offer
|
|
34
|
+
* seen when it resolves.
|
|
35
|
+
*/
|
|
36
|
+
readonly onTurnOn: () => Promise<void>;
|
|
37
|
+
/** The decline button ("No, I’ll just use slash commands"), Escape, the backdrop and Done: marks the offer seen and closes it. */
|
|
38
|
+
readonly onDismiss: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type SaveState =
|
|
42
|
+
| { readonly kind: 'idle' }
|
|
43
|
+
| { readonly kind: 'saving' }
|
|
44
|
+
| { readonly kind: 'saved' }
|
|
45
|
+
| { readonly kind: 'failed'; readonly message: string };
|
|
46
|
+
|
|
47
|
+
const SECONDARY_BUTTON_CLASS =
|
|
48
|
+
'inline-flex min-h-9 items-center gap-2 rounded-lg border border-border bg-surface-0/40 px-3 text-sm font-medium text-foreground outline-none transition-colors motion-reduce:transition-none hover:bg-surface-1/70 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-card disabled:cursor-not-allowed disabled:opacity-50';
|
|
49
|
+
|
|
50
|
+
const PRIMARY_BUTTON_CLASS =
|
|
51
|
+
'min-h-9 rounded-lg bg-primary px-5 text-sm font-medium text-primary-foreground outline-none transition-opacity motion-reduce:transition-none hover:opacity-90 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-card disabled:cursor-not-allowed disabled:opacity-50';
|
|
52
|
+
|
|
53
|
+
export function AgentToolAnnouncementDialog({
|
|
54
|
+
isOpen,
|
|
55
|
+
host,
|
|
56
|
+
onTurnOn,
|
|
57
|
+
onDismiss,
|
|
58
|
+
}: AgentToolAnnouncementDialogProps) {
|
|
59
|
+
const notNowRef = useRef<HTMLButtonElement>(null);
|
|
60
|
+
const doneRef = useRef<HTMLButtonElement>(null);
|
|
61
|
+
const previousFocusRef = useRef<HTMLElement | null>(null);
|
|
62
|
+
const onDismissRef = useRef(onDismiss);
|
|
63
|
+
const [save, setSave] = useState<SaveState>({ kind: 'idle' });
|
|
64
|
+
const saveRef = useRef(save);
|
|
65
|
+
saveRef.current = save;
|
|
66
|
+
const mountedRef = useRef(true);
|
|
67
|
+
const agent = agentToolHostName(host);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
onDismissRef.current = onDismiss;
|
|
71
|
+
}, [onDismiss]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
mountedRef.current = true;
|
|
75
|
+
return () => {
|
|
76
|
+
mountedRef.current = false;
|
|
77
|
+
};
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
// Never closed mid-save: the outcome has to be shown.
|
|
81
|
+
const dismiss = useCallback(() => {
|
|
82
|
+
if (saveRef.current.kind === 'saving') return;
|
|
83
|
+
onDismissRef.current();
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const turnOn = useCallback(async () => {
|
|
87
|
+
if (saveRef.current.kind === 'saving' || saveRef.current.kind === 'saved') return;
|
|
88
|
+
setSave({ kind: 'saving' });
|
|
89
|
+
try {
|
|
90
|
+
await onTurnOn();
|
|
91
|
+
if (mountedRef.current) setSave({ kind: 'saved' });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (!mountedRef.current) return;
|
|
94
|
+
setSave({
|
|
95
|
+
kind: 'failed',
|
|
96
|
+
message: error instanceof Error && error.message ? error.message : 'Could not save the setting.',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}, [onTurnOn]);
|
|
100
|
+
|
|
101
|
+
// Focus follows the outcome: Done once saved.
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (save.kind === 'saved') doneRef.current?.focus();
|
|
104
|
+
}, [save.kind]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!isOpen) return;
|
|
108
|
+
|
|
109
|
+
previousFocusRef.current = document.activeElement instanceof HTMLElement
|
|
110
|
+
? document.activeElement
|
|
111
|
+
: null;
|
|
112
|
+
// The decline button takes focus, so a stray Enter or Space never changes a setting.
|
|
113
|
+
notNowRef.current?.focus();
|
|
114
|
+
|
|
115
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
116
|
+
if (event.key === 'Escape') {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
event.stopPropagation();
|
|
119
|
+
dismiss();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Both apps submit their decision on Mod+Enter from a window-level
|
|
123
|
+
// handler; swallowing it here on the capture phase keeps a keystroke
|
|
124
|
+
// aimed at this dialog from approving a plan or posting a review.
|
|
125
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (event.key !== 'Tab') return;
|
|
131
|
+
|
|
132
|
+
const dialog = document.querySelector<HTMLElement>('[data-agent-tool-announcement-dialog]');
|
|
133
|
+
const focusable = Array.from(
|
|
134
|
+
dialog?.querySelectorAll<HTMLElement>('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])')
|
|
135
|
+
?? [],
|
|
136
|
+
).filter((element) => element.getAttribute('tabindex') !== '-1');
|
|
137
|
+
if (focusable.length === 0) return;
|
|
138
|
+
|
|
139
|
+
const first = focusable[0];
|
|
140
|
+
const last = focusable[focusable.length - 1];
|
|
141
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
last.focus();
|
|
144
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
first.focus();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
151
|
+
return () => {
|
|
152
|
+
document.removeEventListener('keydown', handleKeyDown, true);
|
|
153
|
+
previousFocusRef.current?.focus();
|
|
154
|
+
previousFocusRef.current = null;
|
|
155
|
+
};
|
|
156
|
+
}, [isOpen, dismiss]);
|
|
157
|
+
|
|
158
|
+
if (!isOpen) return null;
|
|
159
|
+
|
|
160
|
+
const saving = save.kind === 'saving';
|
|
161
|
+
return createPortal(
|
|
162
|
+
<div
|
|
163
|
+
className="fixed inset-0 z-[100] flex items-center justify-center bg-background/90 p-4 backdrop-blur-sm"
|
|
164
|
+
onMouseDown={(event) => {
|
|
165
|
+
if (event.target === event.currentTarget) dismiss();
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<div
|
|
169
|
+
data-agent-tool-announcement-dialog
|
|
170
|
+
data-agent-tool-host={host}
|
|
171
|
+
role="dialog"
|
|
172
|
+
aria-modal="true"
|
|
173
|
+
aria-labelledby="agent-tool-announcement-title"
|
|
174
|
+
aria-describedby="agent-tool-announcement-description"
|
|
175
|
+
className="flex max-h-[calc(100dvh-2rem)] w-full max-w-lg min-w-0 flex-col overflow-hidden rounded-xl border border-border bg-card shadow-2xl"
|
|
176
|
+
>
|
|
177
|
+
<style>{DIALOG_LAYOUT_CSS}</style>
|
|
178
|
+
<div className="pn-ato-graphic shrink-0 border-b border-border px-4 py-3" aria-hidden="true">
|
|
179
|
+
<RoundTripGraphic agent={agent} />
|
|
180
|
+
</div>
|
|
181
|
+
<div className="min-h-0 overflow-y-auto px-5 py-5 sm:px-7 sm:py-6">
|
|
182
|
+
<h2 id="agent-tool-announcement-title" className="text-balance text-xl font-semibold tracking-tight sm:text-2xl">
|
|
183
|
+
Do you use Plannotator as a skill?
|
|
184
|
+
</h2>
|
|
185
|
+
<div id="agent-tool-announcement-description">
|
|
186
|
+
<p className="mt-2 text-pretty text-sm leading-relaxed text-muted-foreground">
|
|
187
|
+
Like asking {agent} to “use Plannotator to show me the HTML plan” or “open a
|
|
188
|
+
Plannotator code review”.
|
|
189
|
+
</p>
|
|
190
|
+
<p className="mt-3 text-pretty text-sm leading-relaxed text-foreground">
|
|
191
|
+
If so, turn on the Plannotator tool. Those requests then open right away while {agent} keeps
|
|
192
|
+
working, and your feedback comes back as a message.
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
<p className="mt-3 text-pretty text-xs leading-relaxed text-muted-foreground" data-agent-tool-cost>
|
|
196
|
+
Adds about 780 tokens to each request.{save.kind === 'saved' ? '' : ` ${startsLine(host)}`}
|
|
197
|
+
</p>
|
|
198
|
+
|
|
199
|
+
<div className="mt-5 flex flex-col gap-3">
|
|
200
|
+
{save.kind === 'saved' ? (
|
|
201
|
+
<p
|
|
202
|
+
role="status"
|
|
203
|
+
data-agent-tool-status="saved"
|
|
204
|
+
className="inline-flex min-w-0 items-center gap-2 text-sm text-foreground"
|
|
205
|
+
>
|
|
206
|
+
<span className="size-2 shrink-0 rounded-full bg-success" aria-hidden="true" />
|
|
207
|
+
Turned on. {startsLine(host)}
|
|
208
|
+
</p>
|
|
209
|
+
) : save.kind === 'failed' ? (
|
|
210
|
+
<p
|
|
211
|
+
role="alert"
|
|
212
|
+
data-agent-tool-status="failed"
|
|
213
|
+
className="inline-flex min-w-0 items-center gap-2 text-sm text-destructive"
|
|
214
|
+
>
|
|
215
|
+
<span className="size-2 shrink-0 rounded-full bg-destructive" aria-hidden="true" />
|
|
216
|
+
{save.message}
|
|
217
|
+
</p>
|
|
218
|
+
) : null}
|
|
219
|
+
|
|
220
|
+
{/* Stacks full-width below sm (secondary first, so focus order matches reading order); one row from sm. */}
|
|
221
|
+
<div className="pn-ato-actions">
|
|
222
|
+
{save.kind === 'saved' ? (
|
|
223
|
+
<button
|
|
224
|
+
ref={doneRef}
|
|
225
|
+
type="button"
|
|
226
|
+
onClick={() => onDismissRef.current()}
|
|
227
|
+
className={`${PRIMARY_BUTTON_CLASS} w-full sm:w-auto`}
|
|
228
|
+
>
|
|
229
|
+
Done
|
|
230
|
+
</button>
|
|
231
|
+
) : (
|
|
232
|
+
<>
|
|
233
|
+
<button
|
|
234
|
+
ref={notNowRef}
|
|
235
|
+
type="button"
|
|
236
|
+
onClick={dismiss}
|
|
237
|
+
disabled={saving}
|
|
238
|
+
data-agent-tool-not-now
|
|
239
|
+
className={`${SECONDARY_BUTTON_CLASS} w-full justify-center text-balance py-1.5 text-center sm:w-auto`}
|
|
240
|
+
>
|
|
241
|
+
No, I’ll just use slash commands
|
|
242
|
+
</button>
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
onClick={() => void turnOn()}
|
|
246
|
+
disabled={saving}
|
|
247
|
+
aria-busy={saving}
|
|
248
|
+
data-agent-tool-turn-on
|
|
249
|
+
className={`${PRIMARY_BUTTON_CLASS} w-full sm:w-auto`}
|
|
250
|
+
>
|
|
251
|
+
{saving ? 'Turning on…' : save.kind === 'failed' ? 'Try again' : 'Yes, turn it on'}
|
|
252
|
+
</button>
|
|
253
|
+
</>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>,
|
|
260
|
+
document.body,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/*
|
|
265
|
+
* Layout rules, the graphic band tint and the graphic colors are plain CSS (theme
|
|
266
|
+
* tokens through var()) rather than Tailwind utilities on purpose: this file
|
|
267
|
+
* is scanned by the review stylesheet, which the portable guides.show viewer
|
|
268
|
+
* shares, and utilities used nowhere else would change the pinned viewer CSS
|
|
269
|
+
* for a dialog the viewer never renders. Same result as the approved markup:
|
|
270
|
+
* the graphic hides below 420px; the buttons stack full-width below `sm`
|
|
271
|
+
* (40rem) and sit in one right-aligned row from it.
|
|
272
|
+
*/
|
|
273
|
+
const DIALOG_LAYOUT_CSS = `
|
|
274
|
+
.pn-ato-graphic { display: none; background-color: color-mix(in oklab, var(--muted) 45%, transparent); }
|
|
275
|
+
@media (min-width: 420px) { .pn-ato-graphic { display: block; } }
|
|
276
|
+
.pn-ato-actions { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
277
|
+
@media (min-width: 40rem) {
|
|
278
|
+
.pn-ato-actions { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-end; }
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
|
|
282
|
+
/** When the change applies on each host; part of the small print and the saved status. */
|
|
283
|
+
function startsLine(host: AgentToolOfferHost): string {
|
|
284
|
+
return host === 'opencode' ? 'Starts the next time OpenCode starts.' : 'Starts with your next Pi session.';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/*
|
|
288
|
+
* The round trip, as inline SVG painted only from theme tokens (var(--token)
|
|
289
|
+
* values from theme.css), so it follows every palette in light and dark.
|
|
290
|
+
* aria-hidden: the copy beside it carries the whole meaning.
|
|
291
|
+
*
|
|
292
|
+
* Motion, in one sentence: your feedback travelling back to the agent is what
|
|
293
|
+
* the tool adds, and drawing that one arrow once shows the direction the
|
|
294
|
+
* static picture only implies. Runs once on mount (900ms draw, then a 200ms
|
|
295
|
+
* fade of its head and label), never loops, and is off under
|
|
296
|
+
* prefers-reduced-motion, where the resting frame is the final one.
|
|
297
|
+
*/
|
|
298
|
+
const ROUND_TRIP_MOTION_CSS = `
|
|
299
|
+
.pn-ato-return { stroke-dasharray: 1; stroke-dashoffset: 0; animation: pn-ato-draw 900ms cubic-bezier(0.23, 1, 0.32, 1) 300ms both; }
|
|
300
|
+
.pn-ato-return-head, .pn-ato-return-label { animation: pn-ato-fade 200ms ease-out 1050ms both; }
|
|
301
|
+
@keyframes pn-ato-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
|
|
302
|
+
@keyframes pn-ato-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
303
|
+
@media (prefers-reduced-motion: reduce) {
|
|
304
|
+
.pn-ato-return, .pn-ato-return-head, .pn-ato-return-label { animation: none; }
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
function RoundTripGraphic({ agent }: { agent: string }) {
|
|
309
|
+
return (
|
|
310
|
+
<svg viewBox="0 0 480 176" className="mx-auto block h-auto w-full font-sans" style={{ maxWidth: 460 }} focusable="false">
|
|
311
|
+
<style>{ROUND_TRIP_MOTION_CSS}</style>
|
|
312
|
+
|
|
313
|
+
{/* The agent: a terminal tile */}
|
|
314
|
+
<g transform="translate(92 52)">
|
|
315
|
+
<rect x="0.75" y="0.75" width="54.5" height="54.5" rx="12" style={{ fill: 'var(--card)', stroke: 'var(--border)' }} strokeWidth="1.5" />
|
|
316
|
+
<path d="M17 21 L25 28 L17 35" style={{ fill: 'none', stroke: 'var(--primary)' }} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
|
317
|
+
<rect x="29" y="33" width="11" height="3" rx="1.5" style={{ fill: 'var(--primary)' }} />
|
|
318
|
+
</g>
|
|
319
|
+
<text x={120} y={140} textAnchor="middle" fontSize="13" fontWeight="600" style={{ fill: 'var(--foreground)' }}>
|
|
320
|
+
{agent} <tspan fontWeight="400" style={{ fill: 'var(--muted-foreground)' }}>keeps working</tspan>
|
|
321
|
+
</text>
|
|
322
|
+
|
|
323
|
+
{/* Plannotator: a document with one highlighted, commented line */}
|
|
324
|
+
<g transform="translate(318 38)">
|
|
325
|
+
<rect x="0.75" y="0.75" width="66.5" height="82.5" rx="10" style={{ fill: 'var(--card)', stroke: 'var(--border)' }} strokeWidth="1.5" />
|
|
326
|
+
<rect x="12" y="15" width="30.8" height="4" rx="2" style={{ fill: 'var(--foreground)' }} fillOpacity="0.55" />
|
|
327
|
+
<rect x="12" y="29" width="44" height="3" rx="1.5" style={{ fill: 'var(--muted-foreground)' }} fillOpacity="0.45" />
|
|
328
|
+
<rect x="9" y="38" width="50" height="11" rx="3" style={{ fill: 'var(--accent)' }} fillOpacity="0.28" />
|
|
329
|
+
<rect x="12" y="42" width="36.1" height="3" rx="1.5" style={{ fill: 'var(--foreground)' }} fillOpacity="0.6" />
|
|
330
|
+
<rect x="12" y="56" width="39.6" height="3" rx="1.5" style={{ fill: 'var(--muted-foreground)' }} fillOpacity="0.45" />
|
|
331
|
+
<rect x="12" y="66" width="24.2" height="3" rx="1.5" style={{ fill: 'var(--muted-foreground)' }} fillOpacity="0.45" />
|
|
332
|
+
<circle cx="64" cy="43" r="7" style={{ fill: 'var(--accent)', stroke: 'var(--card)' }} strokeWidth="2" />
|
|
333
|
+
</g>
|
|
334
|
+
<text x={352} y={140} textAnchor="middle" fontSize="13" fontWeight="600" style={{ fill: 'var(--foreground)' }}>Plannotator</text>
|
|
335
|
+
|
|
336
|
+
{/* opens right away */}
|
|
337
|
+
<path d="M152 64 Q 236 16 310 56" style={{ fill: 'none', stroke: 'var(--muted-foreground)' }} strokeWidth="1.75" strokeLinecap="round" />
|
|
338
|
+
<path d="M302.5 47.5 L311 56.5 L299 59" style={{ fill: 'none', stroke: 'var(--muted-foreground)' }} strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
|
339
|
+
<text x={232} y={28} textAnchor="middle" fontSize="12" style={{ fill: 'var(--muted-foreground)' }}>opens right away</text>
|
|
340
|
+
|
|
341
|
+
{/* your feedback comes back */}
|
|
342
|
+
<path d="M310 106 Q 236 150 152 98" pathLength={1} className="pn-ato-return" style={{ fill: 'none', stroke: 'var(--primary)' }} strokeWidth="2.25" strokeLinecap="round" />
|
|
343
|
+
<path d="M153 112 L151.5 98.5 L164.5 100" className="pn-ato-return-head" style={{ fill: 'none', stroke: 'var(--primary)' }} strokeWidth="2.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
344
|
+
<text x={236} y={166} textAnchor="middle" fontSize="12" fontWeight="600" className="pn-ato-return-label" style={{ fill: 'var(--primary)' }}>your feedback, as a message</text>
|
|
345
|
+
</svg>
|
|
346
|
+
);
|
|
347
|
+
}
|
package/components/AgentsTab.tsx
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
effortSelectOptions,
|
|
32
32
|
modelLabel,
|
|
33
33
|
modelSelectOptions,
|
|
34
|
+
PI_THINKING_LEVELS,
|
|
34
35
|
type CatalogModel,
|
|
35
36
|
} from '@plannotator/core/model-catalog';
|
|
36
37
|
import { useModelCatalogs, type ModelCatalogs } from '../hooks/useModelCatalogs';
|
|
@@ -75,15 +76,14 @@ const COPILOT_MODELS: Array<{ value: string; label: string }> = [
|
|
|
75
76
|
];
|
|
76
77
|
|
|
77
78
|
// Pi's unified reasoning knob (`--thinking`), applied to whichever model is
|
|
78
|
-
// selected
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{ value:
|
|
86
|
-
];
|
|
79
|
+
// selected; pi clamps a level the model does not support. Pi's own level
|
|
80
|
+
// vocabulary minus `max`, which pi only accepts on `--thinking` since 0.80.6
|
|
81
|
+
// (an older pi would reject the whole job). Short labels fit the segmented
|
|
82
|
+
// picker.
|
|
83
|
+
const PI_THINKING_SHORT_LABELS: Record<string, string> = { minimal: 'Min', medium: 'Med' };
|
|
84
|
+
export const PI_THINKING: Array<{ value: string; label: string }> = PI_THINKING_LEVELS
|
|
85
|
+
.filter((level) => level !== 'max')
|
|
86
|
+
.map((level) => ({ value: level, label: PI_THINKING_SHORT_LABELS[level] ?? EFFORT_LABELS[level] ?? level }));
|
|
87
87
|
|
|
88
88
|
const MODE_LABEL: Record<AgentMode, string> = {
|
|
89
89
|
review: 'Code Review',
|