Prune files not in the deploy set (stale build context)
Browse files- web/src/ai-agent/AiAgentPage.tsx +0 -237
- web/src/ai-agent/aiAgent.css +0 -279
- web/src/ai-agent/aiAgentApi.ts +0 -197
web/src/ai-agent/AiAgentPage.tsx
DELETED
|
@@ -1,237 +0,0 @@
|
|
| 1 |
-
// ---------------------------------------------------------------------------
|
| 2 |
-
// ai-agent/AiAgentPage.tsx — WAVE 33 · W33-T54/T55 (owner item 7, ruling R3).
|
| 3 |
-
//
|
| 4 |
-
// Owner: *"collapse the web browser into an AI agent module that builds the flow
|
| 5 |
-
// for you"*, and *"lay out the foundation with custom tools we provide"*. The
|
| 6 |
-
// tools are the action catalog. This page is the door to them: one box, one
|
| 7 |
-
// draft, one Accept.
|
| 8 |
-
//
|
| 9 |
-
// ⛔ THE DRAFT IS SHOWN BEFORE ANYTHING IS SAVED, and that is T54's `done-when`
|
| 10 |
-
// rather than a nicety. The one failure class no validator can catch is a
|
| 11 |
-
// well-formed flow that does something OTHER than what was asked for, and the
|
| 12 |
-
// only thing that catches it is a person reading the steps. So `draftFlow`
|
| 13 |
-
// writes nothing, the steps are painted in full, and `Accept` is a separate
|
| 14 |
-
// click through the ORDINARY create door.
|
| 15 |
-
//
|
| 16 |
-
// ⛔ AND WHAT `clean_actions` CHANGED IS SHOWN TOO. It drops a config key it does
|
| 17 |
-
// not recognise and answers 200 (D-75), so a draft accepted without that
|
| 18 |
-
// disclosure would open as a flow the person was never shown. `dropped` is
|
| 19 |
-
// painted as plainly as the steps are.
|
| 20 |
-
//
|
| 21 |
-
// ⚠ C4: no `index.css` rule is added or changed here. This module ships its own
|
| 22 |
-
// co-located sheet, which is the established practice (`query.css`,
|
| 23 |
-
// `SwipeView.css`, `navExtras.css`) and the contract for every lane but B.
|
| 24 |
-
// ---------------------------------------------------------------------------
|
| 25 |
-
|
| 26 |
-
import { useState } from "react";
|
| 27 |
-
|
| 28 |
-
import { acceptDraft, draftFlow, type Draft, type DraftResult } from "./aiAgentApi";
|
| 29 |
-
import "./aiAgent.css";
|
| 30 |
-
|
| 31 |
-
/** The examples under the box. Deliberately three, deliberately short, deliberately concrete. */
|
| 32 |
-
const EXAMPLES = [
|
| 33 |
-
"Go to each supplier's website and read the price into the Price column",
|
| 34 |
-
"Every Monday, find the new rows in Leads and enrich the Instagram profiles",
|
| 35 |
-
"Open each product page, click Accept cookies, then read the stock count",
|
| 36 |
-
];
|
| 37 |
-
|
| 38 |
-
export default function AiAgentPage() {
|
| 39 |
-
const [prompt, setPrompt] = useState("");
|
| 40 |
-
const [busy, setBusy] = useState(false);
|
| 41 |
-
const [problem, setProblem] = useState("");
|
| 42 |
-
const [result, setResult] = useState<DraftResult | null>(null);
|
| 43 |
-
const [saved, setSaved] = useState("");
|
| 44 |
-
|
| 45 |
-
const ask = async (text?: string) => {
|
| 46 |
-
const q = (text ?? prompt).trim();
|
| 47 |
-
if (!q || busy) return;
|
| 48 |
-
setBusy(true);
|
| 49 |
-
setProblem("");
|
| 50 |
-
setResult(null);
|
| 51 |
-
setSaved("");
|
| 52 |
-
const r = await draftFlow(q);
|
| 53 |
-
setBusy(false);
|
| 54 |
-
if (!r.ok) {
|
| 55 |
-
// ⚠ THE SERVER'S OWN SENTENCE, PRINTED. A refusal here is usually the model saying "this
|
| 56 |
-
// needs a step this tool does not have", which is the most useful thing on the screen —
|
| 57 |
-
// rewriting it into "something went wrong" would throw away the only answer.
|
| 58 |
-
setProblem(r.message);
|
| 59 |
-
return;
|
| 60 |
-
}
|
| 61 |
-
setResult(r.value);
|
| 62 |
-
};
|
| 63 |
-
|
| 64 |
-
const accept = async (draft: Draft) => {
|
| 65 |
-
if (busy) return;
|
| 66 |
-
setBusy(true);
|
| 67 |
-
setProblem("");
|
| 68 |
-
const r = await acceptDraft(draft);
|
| 69 |
-
setBusy(false);
|
| 70 |
-
if (!r.ok) {
|
| 71 |
-
setProblem(r.message);
|
| 72 |
-
return;
|
| 73 |
-
}
|
| 74 |
-
setSaved(r.value.id);
|
| 75 |
-
};
|
| 76 |
-
|
| 77 |
-
return (
|
| 78 |
-
<div className="aiagent">
|
| 79 |
-
<h2 className="aiagent-h">Describe what you want, and I will build the steps</h2>
|
| 80 |
-
<p className="aiagent-sub">
|
| 81 |
-
I can only use the steps this tool already has. If what you ask for needs something that
|
| 82 |
-
is not built, I will say so rather than guess.
|
| 83 |
-
</p>
|
| 84 |
-
|
| 85 |
-
<textarea
|
| 86 |
-
id="aiagent-prompt"
|
| 87 |
-
className="aiagent-box"
|
| 88 |
-
rows={3}
|
| 89 |
-
placeholder="Go to this website, do this…"
|
| 90 |
-
value={prompt}
|
| 91 |
-
disabled={busy}
|
| 92 |
-
onChange={(e) => setPrompt(e.target.value)}
|
| 93 |
-
/>
|
| 94 |
-
<div className="aiagent-actions">
|
| 95 |
-
<button
|
| 96 |
-
type="button"
|
| 97 |
-
className="aiagent-go"
|
| 98 |
-
disabled={busy || !prompt.trim()}
|
| 99 |
-
onClick={() => void ask()}
|
| 100 |
-
>
|
| 101 |
-
{busy ? "Thinking…" : "Draft the steps"}
|
| 102 |
-
</button>
|
| 103 |
-
{/* ⚠ SAYS WHAT IT WILL NOT DO. "Draft" is a promise about writing, and a person about to
|
| 104 |
-
hand a description to an AI is entitled to know the answer is not going live. */}
|
| 105 |
-
<span className="aiagent-note">Nothing is saved until you accept it.</span>
|
| 106 |
-
</div>
|
| 107 |
-
|
| 108 |
-
{!result && !problem ? (
|
| 109 |
-
<div className="aiagent-eg">
|
| 110 |
-
<p className="aiagent-eg-h">For example</p>
|
| 111 |
-
{EXAMPLES.map((e) => (
|
| 112 |
-
<button
|
| 113 |
-
key={e}
|
| 114 |
-
type="button"
|
| 115 |
-
className="aiagent-eg-row"
|
| 116 |
-
disabled={busy}
|
| 117 |
-
onClick={() => {
|
| 118 |
-
setPrompt(e);
|
| 119 |
-
void ask(e);
|
| 120 |
-
}}
|
| 121 |
-
>
|
| 122 |
-
{e}
|
| 123 |
-
</button>
|
| 124 |
-
))}
|
| 125 |
-
</div>
|
| 126 |
-
) : null}
|
| 127 |
-
|
| 128 |
-
{problem ? <p className="aiagent-problem">{problem}</p> : null}
|
| 129 |
-
|
| 130 |
-
{result ? (
|
| 131 |
-
<div className="aiagent-draft">
|
| 132 |
-
<h3 className="aiagent-draft-h">{result.draft.name}</h3>
|
| 133 |
-
<p className="aiagent-draft-sub">
|
| 134 |
-
Starts on: {result.draft.trigger || "manual"}
|
| 135 |
-
{result.draft.table ? ` · walks ${result.draft.table}` : ""}
|
| 136 |
-
{result.provider ? ` · drafted by ${result.provider}` : ""}
|
| 137 |
-
</p>
|
| 138 |
-
|
| 139 |
-
<ol className="aiagent-steps">
|
| 140 |
-
{result.draft.actions.map((a, i) => (
|
| 141 |
-
<li key={a.id} className="aiagent-step">
|
| 142 |
-
<span className="aiagent-step-n">{i + 1}</span>
|
| 143 |
-
<div className="aiagent-step-body">
|
| 144 |
-
<p className="aiagent-step-kind">{a.kind}</p>
|
| 145 |
-
{a.why ? <p className="aiagent-step-why">{a.why}</p> : null}
|
| 146 |
-
{/* ⛔ THE CONFIG IS PAINTED, NOT SUMMARISED. "Reads a page" tells a person
|
| 147 |
-
nothing they can check; the URL and the selector are the whole of what they
|
| 148 |
-
are being asked to approve. */}
|
| 149 |
-
{Object.entries(a.config)
|
| 150 |
-
.filter(([, v]) => v !== "" && v !== false && v !== null && v !== undefined)
|
| 151 |
-
.map(([k, v]) => (
|
| 152 |
-
<p key={k} className="aiagent-step-cfg">
|
| 153 |
-
<span className="aiagent-step-key">{k}</span> {String(v)}
|
| 154 |
-
</p>
|
| 155 |
-
))}
|
| 156 |
-
{/*
|
| 157 |
-
⛔⛔ THE BLANKS, SHOWN. Skipping empty values is right on its own, and the
|
| 158 |
-
system prompt telling the model to leave a setting blank rather than invent it
|
| 159 |
-
is right on its own — together they made an UNFINISHED step paint as a finished
|
| 160 |
-
one: a `web_read` with no selector and no target column rendered as url + attr
|
| 161 |
-
+ timeout and nothing else, and the create door then accepted it. A person
|
| 162 |
-
approved a step that reads nothing into nowhere, having been shown no blank.
|
| 163 |
-
Found by the verifier on this ticket, not by a gate.
|
| 164 |
-
⚠ The words are the SERVER's (`action_needs`), so this line, the builder's
|
| 165 |
-
Configured label and the run refusal all say the same thing.
|
| 166 |
-
*/}
|
| 167 |
-
{a.needs.length ? (
|
| 168 |
-
<p className="aiagent-step-needs">
|
| 169 |
-
Still needs {a.needs.join(", ")} — you can fill it in after you accept.
|
| 170 |
-
</p>
|
| 171 |
-
) : null}
|
| 172 |
-
{a.unknownField ? (
|
| 173 |
-
<p className="aiagent-step-needs">
|
| 174 |
-
There is no column called {a.unknownField} on that database, so this step
|
| 175 |
-
would write nowhere. Pick the column after you accept.
|
| 176 |
-
</p>
|
| 177 |
-
) : null}
|
| 178 |
-
</div>
|
| 179 |
-
</li>
|
| 180 |
-
))}
|
| 181 |
-
</ol>
|
| 182 |
-
|
| 183 |
-
{/* ⛔ WHOLE-DRAFT DISCLOSURES — a step list the door truncated, a trigger this
|
| 184 |
-
deployment does not have. Standing rule (W30/R6): a limit that cannot be removed
|
| 185 |
-
must be REPORTED with its cause and a recommended fix, and a silent truncation IS
|
| 186 |
-
the violation. The draft door caps at 12 steps while the engine stores 20, so a
|
| 187 |
-
longer answer lost steps that would have been storable. */}
|
| 188 |
-
{result.notes.length ? (
|
| 189 |
-
<div className="aiagent-dropped">
|
| 190 |
-
{result.notes.map((n) => (
|
| 191 |
-
<p key={n} className="aiagent-dropped-h">{n}</p>
|
| 192 |
-
))}
|
| 193 |
-
</div>
|
| 194 |
-
) : null}
|
| 195 |
-
|
| 196 |
-
{result.dropped.length ? (
|
| 197 |
-
<div className="aiagent-dropped">
|
| 198 |
-
<p className="aiagent-dropped-h">
|
| 199 |
-
Some settings were changed to fit what this tool accepts:
|
| 200 |
-
</p>
|
| 201 |
-
{result.dropped.map((d) => (
|
| 202 |
-
<p key={d.kind + d.keys.join()} className="aiagent-dropped-row">
|
| 203 |
-
{d.kind}: {d.keys.join(", ")}
|
| 204 |
-
</p>
|
| 205 |
-
))}
|
| 206 |
-
</div>
|
| 207 |
-
) : null}
|
| 208 |
-
|
| 209 |
-
{saved ? (
|
| 210 |
-
<p className="aiagent-saved">
|
| 211 |
-
Saved. It is an ordinary automation now — open it from Automation to edit any step.
|
| 212 |
-
</p>
|
| 213 |
-
) : (
|
| 214 |
-
<div className="aiagent-actions">
|
| 215 |
-
<button
|
| 216 |
-
type="button"
|
| 217 |
-
className="aiagent-go"
|
| 218 |
-
disabled={busy}
|
| 219 |
-
onClick={() => void accept(result.draft)}
|
| 220 |
-
>
|
| 221 |
-
Accept and create it
|
| 222 |
-
</button>
|
| 223 |
-
<button
|
| 224 |
-
type="button"
|
| 225 |
-
className="aiagent-again"
|
| 226 |
-
disabled={busy}
|
| 227 |
-
onClick={() => void ask()}
|
| 228 |
-
>
|
| 229 |
-
Try again
|
| 230 |
-
</button>
|
| 231 |
-
</div>
|
| 232 |
-
)}
|
| 233 |
-
</div>
|
| 234 |
-
) : null}
|
| 235 |
-
</div>
|
| 236 |
-
);
|
| 237 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/src/ai-agent/aiAgent.css
DELETED
|
@@ -1,279 +0,0 @@
|
|
| 1 |
-
/* ---------------------------------------------------------------------------
|
| 2 |
-
ai-agent/aiAgent.css — the AI agent module's own stylesheet
|
| 3 |
-
(WAVE 33, owner item 7, ruling R3).
|
| 4 |
-
|
| 5 |
-
Shipped BESIDE its component, following `query/query.css` and the
|
| 6 |
-
`FormInterface.css` / `SwipeView.css` precedent, rather than added to
|
| 7 |
-
`index.css` — which is B's file this wave (contract C4). Vite carries the
|
| 8 |
-
import; the node shim in `verify_ui` already stubs a CSS require for exactly
|
| 9 |
-
this shape.
|
| 10 |
-
|
| 11 |
-
Deliberately shaped like the Query page it sits beside: same page padding,
|
| 12 |
-
same card surfaces, same title scale. The two are the same kind of thing —
|
| 13 |
-
describe something in words, read what came back before it is committed — and
|
| 14 |
-
a person should not have to learn two layouts to do it.
|
| 15 |
-
|
| 16 |
-
⛔ EVERY FONT SIZE IS A `--lp-fs-*` TOKEN AND EVERY COLOUR A `--lp-*` TOKEN.
|
| 17 |
-
Wave-21 R5 is gated over every stylesheet under src, not just index.css, and
|
| 18 |
-
one literal here reds `web_ui` for the whole tree.
|
| 19 |
-
|
| 20 |
-
⛔ NO EMOJIS AND NO ALL-CAPS CHROME (DESIGN.md R6).
|
| 21 |
-
|
| 22 |
-
⛔⛔ AND NOTHING IN THIS COMMENT MAY CONTAIN THE TWO CHARACTERS THAT CLOSE IT.
|
| 23 |
-
A path glob written out in a sibling stylesheet's header spelled the
|
| 24 |
-
terminator, the comment ended mid-sentence, and `lightningcss` then died on
|
| 25 |
-
the first backtick after it. `tsc` passed and vite transformed every module;
|
| 26 |
-
the failure surfaced in the MINIFIER, nowhere near its cause.
|
| 27 |
-
--------------------------------------------------------------------------- */
|
| 28 |
-
|
| 29 |
-
.aiagent {
|
| 30 |
-
height: 100%;
|
| 31 |
-
overflow-y: auto;
|
| 32 |
-
padding: 34px 44px 56px;
|
| 33 |
-
box-sizing: border-box;
|
| 34 |
-
background: var(--lp-wash);
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
.aiagent-h {
|
| 38 |
-
margin: 0 0 6px;
|
| 39 |
-
font-size: var(--lp-fs-lg);
|
| 40 |
-
font-weight: 650;
|
| 41 |
-
color: var(--lp-ink);
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
.aiagent-sub {
|
| 45 |
-
margin: 0 0 18px;
|
| 46 |
-
max-width: 62ch;
|
| 47 |
-
font-size: var(--lp-fs-sm);
|
| 48 |
-
line-height: var(--lp-lh);
|
| 49 |
-
color: var(--lp-muted);
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
.aiagent-box {
|
| 53 |
-
display: block;
|
| 54 |
-
width: 100%;
|
| 55 |
-
max-width: 74ch;
|
| 56 |
-
padding: 12px 14px;
|
| 57 |
-
box-sizing: border-box;
|
| 58 |
-
font: inherit;
|
| 59 |
-
font-size: var(--lp-fs-sm);
|
| 60 |
-
line-height: var(--lp-lh);
|
| 61 |
-
color: var(--lp-ink);
|
| 62 |
-
background: var(--lp-surface);
|
| 63 |
-
border: 1px solid var(--lp-line);
|
| 64 |
-
border-radius: var(--lp-r-md);
|
| 65 |
-
resize: vertical;
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.aiagent-box:focus {
|
| 69 |
-
outline: none;
|
| 70 |
-
border-color: var(--lp-blue-solid);
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
.aiagent-actions {
|
| 74 |
-
display: flex;
|
| 75 |
-
align-items: center;
|
| 76 |
-
gap: 12px;
|
| 77 |
-
margin: 12px 0 0;
|
| 78 |
-
flex-wrap: wrap;
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
.aiagent-go {
|
| 82 |
-
padding: 8px 16px;
|
| 83 |
-
font: inherit;
|
| 84 |
-
font-size: var(--lp-fs-sm);
|
| 85 |
-
font-weight: 600;
|
| 86 |
-
color: var(--lp-surface);
|
| 87 |
-
background: var(--lp-blue-solid);
|
| 88 |
-
border: 1px solid var(--lp-blue-solid);
|
| 89 |
-
border-radius: var(--lp-r-md);
|
| 90 |
-
cursor: pointer;
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
.aiagent-go:disabled {
|
| 94 |
-
opacity: 0.5;
|
| 95 |
-
cursor: default;
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
.aiagent-again {
|
| 99 |
-
padding: 8px 16px;
|
| 100 |
-
font: inherit;
|
| 101 |
-
font-size: var(--lp-fs-sm);
|
| 102 |
-
color: var(--lp-ink);
|
| 103 |
-
background: var(--lp-surface);
|
| 104 |
-
border: 1px solid var(--lp-line);
|
| 105 |
-
border-radius: var(--lp-r-md);
|
| 106 |
-
cursor: pointer;
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
.aiagent-again:disabled {
|
| 110 |
-
opacity: 0.5;
|
| 111 |
-
cursor: default;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
.aiagent-note {
|
| 115 |
-
font-size: var(--lp-fs-2xs);
|
| 116 |
-
color: var(--lp-muted);
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
.aiagent-eg {
|
| 120 |
-
margin: 26px 0 0;
|
| 121 |
-
max-width: 74ch;
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
.aiagent-eg-h {
|
| 125 |
-
margin: 0 0 8px;
|
| 126 |
-
font-size: var(--lp-fs-2xs);
|
| 127 |
-
color: var(--lp-muted);
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.aiagent-eg-row {
|
| 131 |
-
display: block;
|
| 132 |
-
width: 100%;
|
| 133 |
-
margin: 0 0 8px;
|
| 134 |
-
padding: 10px 14px;
|
| 135 |
-
text-align: left;
|
| 136 |
-
font: inherit;
|
| 137 |
-
font-size: var(--lp-fs-sm);
|
| 138 |
-
color: var(--lp-ink);
|
| 139 |
-
background: var(--lp-surface);
|
| 140 |
-
border: 1px solid var(--lp-line);
|
| 141 |
-
border-radius: var(--lp-r-md);
|
| 142 |
-
cursor: pointer;
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
.aiagent-eg-row:disabled {
|
| 146 |
-
opacity: 0.5;
|
| 147 |
-
cursor: default;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
.aiagent-problem {
|
| 151 |
-
margin: 18px 0 0;
|
| 152 |
-
max-width: 74ch;
|
| 153 |
-
padding: 10px 14px;
|
| 154 |
-
font-size: var(--lp-fs-sm);
|
| 155 |
-
line-height: var(--lp-lh);
|
| 156 |
-
color: var(--lp-red-deep);
|
| 157 |
-
background: var(--lp-surface);
|
| 158 |
-
border: 1px solid var(--lp-red-deep);
|
| 159 |
-
border-radius: var(--lp-r-md);
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
.aiagent-draft {
|
| 163 |
-
margin: 26px 0 0;
|
| 164 |
-
max-width: 74ch;
|
| 165 |
-
padding: 18px 20px;
|
| 166 |
-
background: var(--lp-surface);
|
| 167 |
-
border: 1px solid var(--lp-line);
|
| 168 |
-
border-radius: var(--lp-r-md);
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
.aiagent-draft-h {
|
| 172 |
-
margin: 0 0 4px;
|
| 173 |
-
font-size: var(--lp-fs-md);
|
| 174 |
-
font-weight: 650;
|
| 175 |
-
color: var(--lp-ink);
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
.aiagent-draft-sub {
|
| 179 |
-
margin: 0 0 16px;
|
| 180 |
-
font-size: var(--lp-fs-2xs);
|
| 181 |
-
color: var(--lp-muted);
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
.aiagent-steps {
|
| 185 |
-
margin: 0;
|
| 186 |
-
padding: 0;
|
| 187 |
-
list-style: none;
|
| 188 |
-
}
|
| 189 |
-
|
| 190 |
-
.aiagent-step {
|
| 191 |
-
display: flex;
|
| 192 |
-
gap: 12px;
|
| 193 |
-
padding: 12px 0;
|
| 194 |
-
border-top: 1px solid var(--lp-line);
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
.aiagent-step-n {
|
| 198 |
-
flex: none;
|
| 199 |
-
width: 22px;
|
| 200 |
-
height: 22px;
|
| 201 |
-
display: inline-flex;
|
| 202 |
-
align-items: center;
|
| 203 |
-
justify-content: center;
|
| 204 |
-
font-size: var(--lp-fs-2xs);
|
| 205 |
-
font-weight: 600;
|
| 206 |
-
color: var(--lp-muted);
|
| 207 |
-
background: var(--lp-surface-2);
|
| 208 |
-
border-radius: 999px;
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
.aiagent-step-body {
|
| 212 |
-
min-width: 0;
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
.aiagent-step-kind {
|
| 216 |
-
margin: 0;
|
| 217 |
-
font-size: var(--lp-fs-sm);
|
| 218 |
-
font-weight: 600;
|
| 219 |
-
color: var(--lp-ink);
|
| 220 |
-
}
|
| 221 |
-
|
| 222 |
-
.aiagent-step-why {
|
| 223 |
-
margin: 2px 0 6px;
|
| 224 |
-
font-size: var(--lp-fs-2xs);
|
| 225 |
-
line-height: var(--lp-lh);
|
| 226 |
-
color: var(--lp-muted);
|
| 227 |
-
}
|
| 228 |
-
|
| 229 |
-
.aiagent-step-cfg {
|
| 230 |
-
margin: 0;
|
| 231 |
-
font-size: var(--lp-fs-2xs);
|
| 232 |
-
line-height: var(--lp-lh);
|
| 233 |
-
color: var(--lp-ink);
|
| 234 |
-
overflow-wrap: anywhere;
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
.aiagent-step-key {
|
| 238 |
-
display: inline-block;
|
| 239 |
-
min-width: 84px;
|
| 240 |
-
color: var(--lp-muted);
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.aiagent-dropped {
|
| 244 |
-
margin: 14px 0 0;
|
| 245 |
-
padding: 10px 14px;
|
| 246 |
-
background: var(--lp-surface-2);
|
| 247 |
-
border-left: 3px solid var(--lp-yellow-deep);
|
| 248 |
-
border-radius: var(--lp-r-md);
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
.aiagent-dropped-h {
|
| 252 |
-
margin: 0 0 4px;
|
| 253 |
-
font-size: var(--lp-fs-2xs);
|
| 254 |
-
font-weight: 600;
|
| 255 |
-
color: var(--lp-ink);
|
| 256 |
-
}
|
| 257 |
-
|
| 258 |
-
.aiagent-dropped-row {
|
| 259 |
-
margin: 0;
|
| 260 |
-
font-size: var(--lp-fs-2xs);
|
| 261 |
-
color: var(--lp-muted);
|
| 262 |
-
}
|
| 263 |
-
|
| 264 |
-
.aiagent-saved {
|
| 265 |
-
margin: 16px 0 0;
|
| 266 |
-
font-size: var(--lp-fs-sm);
|
| 267 |
-
line-height: var(--lp-lh);
|
| 268 |
-
color: var(--lp-ink);
|
| 269 |
-
}
|
| 270 |
-
|
| 271 |
-
/* The per-step disclosures: a required setting the assistant left blank (it was told to leave one
|
| 272 |
-
blank rather than invent it), and a column the target database does not have. Toned like a
|
| 273 |
-
warning rather than an error, because neither stops the draft being accepted and fixed. */
|
| 274 |
-
.aiagent-step-needs {
|
| 275 |
-
margin: 4px 0 0;
|
| 276 |
-
font-size: var(--lp-fs-2xs);
|
| 277 |
-
line-height: var(--lp-lh);
|
| 278 |
-
color: var(--lp-yellow-deep);
|
| 279 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/src/ai-agent/aiAgentApi.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
| 1 |
-
// ---------------------------------------------------------------------------
|
| 2 |
-
// ai-agent/aiAgentApi.ts — WAVE 33 · W33-T54/T55 (owner item 7, ruling R3):
|
| 3 |
-
// two calls, and nothing else.
|
| 4 |
-
//
|
| 5 |
-
// Shaped like `query/queryApi.ts` — the same `Result<T>`, the same 4xx/5xx
|
| 6 |
-
// message split — because the failure that matters is identical: a wrong
|
| 7 |
-
// `credentials` word produces no client-side symptom at all, just a 401 from a
|
| 8 |
-
// server that never saw a session.
|
| 9 |
-
//
|
| 10 |
-
// ⛔ DRAFTING AND SAVING ARE TWO DIFFERENT DOORS, AND THAT IS RULING R3, NOT A
|
| 11 |
-
// convenience. `draftFlow` writes NOTHING; accepting a draft calls the ORDINARY
|
| 12 |
-
// `POST /automations` that the New-automation button has always called. R3 asks
|
| 13 |
-
// for a flow "indistinguishable from a hand-built one", and the way to get that
|
| 14 |
-
// is for there to be no second write path — not for a second one to be written
|
| 15 |
-
// carefully enough to look the same.
|
| 16 |
-
//
|
| 17 |
-
// ⚠ NO CLIENT UNION OVER `kind`. The action kinds are the server's vocabulary
|
| 18 |
-
// and arrive as strings (the wave-9 law): a client
|
| 19 |
-
// `type Kind = "web_read" | …` turns "the server added a kind" into "the client
|
| 20 |
-
// drops the step".
|
| 21 |
-
// ---------------------------------------------------------------------------
|
| 22 |
-
|
| 23 |
-
import { API_V1, CREDENTIALS } from "../apiContract";
|
| 24 |
-
|
| 25 |
-
export type Result<T> =
|
| 26 |
-
| { ok: true; value: T }
|
| 27 |
-
| { ok: false; status: number; message: string };
|
| 28 |
-
|
| 29 |
-
/** One step of a drafted flow — the CLEANED config, plus the model's own reason for it. */
|
| 30 |
-
export interface DraftAction {
|
| 31 |
-
id: string;
|
| 32 |
-
/** A STRING, never a union — see the header. */
|
| 33 |
-
kind: string;
|
| 34 |
-
config: Record<string, unknown>;
|
| 35 |
-
/** One sentence: why the assistant put this step here. May be "". */
|
| 36 |
-
why: string;
|
| 37 |
-
/**
|
| 38 |
-
* ⛔ WHAT THIS STEP IS STILL MISSING, in the words the runner uses — straight off the server's
|
| 39 |
-
* `action_needs`, the same predicate the builder's Configured label and the run refusal read.
|
| 40 |
-
* The system prompt tells the model to leave a setting BLANK rather than invent it, and this
|
| 41 |
-
* page skips empty values; together those two right decisions made an unfinished step paint as
|
| 42 |
-
* a finished one, and the create door then accepted it. Empty is the ordinary case.
|
| 43 |
-
*/
|
| 44 |
-
needs: string[];
|
| 45 |
-
/**
|
| 46 |
-
* ⛔ A COLUMN THE TARGET DATABASE DOES NOT HAVE, when the assistant named one. Usually the
|
| 47 |
-
* column's LABEL where its key was wanted — which is what a person would say, so it is what an
|
| 48 |
-
* obedient model echoes, and nothing validated it at draft, accept or store.
|
| 49 |
-
*/
|
| 50 |
-
unknownField?: string;
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
export interface Draft {
|
| 54 |
-
name: string;
|
| 55 |
-
trigger: string;
|
| 56 |
-
table: string;
|
| 57 |
-
actions: DraftAction[];
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
export interface DraftResult {
|
| 61 |
-
draft: Draft;
|
| 62 |
-
/** Which rung answered — a wrong draft has to be diagnosable after the fact. */
|
| 63 |
-
provider: string;
|
| 64 |
-
/**
|
| 65 |
-
* ⛔ CONTRACT C7's DISCLOSURE CHANNEL. `clean_actions` drops a config key it does not recognise
|
| 66 |
-
* and answers 200 (D-75), so without this the person would accept a flow, open it, and find a
|
| 67 |
-
* step configured differently from the one they were shown. Empty is the ordinary case.
|
| 68 |
-
*/
|
| 69 |
-
dropped: { kind: string; keys: string[] }[];
|
| 70 |
-
/**
|
| 71 |
-
* ⛔ WHOLE-DRAFT DISCLOSURES — a truncated step list, a trigger this deployment does not have.
|
| 72 |
-
* Standing rule (owner, W30/R6): a limit that cannot be removed must be REPORTED with its cause
|
| 73 |
-
* and a recommended fix. The draft door caps a flow at 12 steps while the engine stores 20, so
|
| 74 |
-
* a longer answer loses steps that WOULD have been storable — silently, until now.
|
| 75 |
-
*/
|
| 76 |
-
notes: string[];
|
| 77 |
-
/** Always false today, and read rather than assumed: the draft door writes nothing. */
|
| 78 |
-
saved: boolean;
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
export function errorMessage(status: number, message?: string): string {
|
| 82 |
-
if (status >= 500 || !message) {
|
| 83 |
-
return status >= 500
|
| 84 |
-
? "Something went wrong on our side. Try again in a moment."
|
| 85 |
-
: `The server answered ${status}.`;
|
| 86 |
-
}
|
| 87 |
-
return message;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
async function call<T>(
|
| 91 |
-
path: string,
|
| 92 |
-
init: RequestInit,
|
| 93 |
-
read: (body: unknown) => T
|
| 94 |
-
): Promise<Result<T>> {
|
| 95 |
-
let res: Response;
|
| 96 |
-
try {
|
| 97 |
-
res = await fetch(`${API_V1}${path}`, { credentials: CREDENTIALS, ...init });
|
| 98 |
-
} catch {
|
| 99 |
-
return { ok: false, status: 0, message: "Cannot reach the server." };
|
| 100 |
-
}
|
| 101 |
-
const body = (await res.json().catch(() => null)) as unknown;
|
| 102 |
-
if (!res.ok) {
|
| 103 |
-
const detail = (body as { error?: { message?: string } } | null)?.error?.message;
|
| 104 |
-
return { ok: false, status: res.status, message: errorMessage(res.status, detail) };
|
| 105 |
-
}
|
| 106 |
-
return { ok: true, value: read(body) };
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
const json = (data: unknown): RequestInit => ({
|
| 110 |
-
method: "POST",
|
| 111 |
-
headers: { "Content-Type": "application/json" },
|
| 112 |
-
body: JSON.stringify(data),
|
| 113 |
-
});
|
| 114 |
-
|
| 115 |
-
const str = (v: unknown): string => (typeof v === "string" ? v : "");
|
| 116 |
-
|
| 117 |
-
/** Fails CLOSED: an unreadable step is dropped, never half-parsed into a config nobody wrote. */
|
| 118 |
-
export function parseDraft(body: unknown): DraftResult {
|
| 119 |
-
const r = (body || {}) as Record<string, unknown>;
|
| 120 |
-
const d = (r.draft || {}) as Record<string, unknown>;
|
| 121 |
-
const rows = Array.isArray(d.actions) ? (d.actions as unknown[]) : [];
|
| 122 |
-
const actions: DraftAction[] = [];
|
| 123 |
-
rows.forEach((row, i) => {
|
| 124 |
-
const a = (row || {}) as Record<string, unknown>;
|
| 125 |
-
if (!str(a.kind)) return;
|
| 126 |
-
actions.push({
|
| 127 |
-
id: str(a.id) || `act_${i + 1}`,
|
| 128 |
-
kind: str(a.kind),
|
| 129 |
-
config: (a.config && typeof a.config === "object"
|
| 130 |
-
? (a.config as Record<string, unknown>)
|
| 131 |
-
: {}),
|
| 132 |
-
why: str(a.why),
|
| 133 |
-
// Fails CLOSED the SAFE way: an unreadable `needs` is an empty list, so the page shows no
|
| 134 |
-
// false alarm — and the create door still refuses at run time either way.
|
| 135 |
-
needs: Array.isArray(a.needs) ? (a.needs as unknown[]).map(str).filter(Boolean) : [],
|
| 136 |
-
...(str(a.unknownField) ? { unknownField: str(a.unknownField) } : {}),
|
| 137 |
-
});
|
| 138 |
-
});
|
| 139 |
-
const dropped: { kind: string; keys: string[] }[] = [];
|
| 140 |
-
(Array.isArray(r.dropped) ? (r.dropped as unknown[]) : []).forEach((row) => {
|
| 141 |
-
const x = (row || {}) as Record<string, unknown>;
|
| 142 |
-
const keys = Array.isArray(x.keys) ? (x.keys as unknown[]).map(str).filter(Boolean) : [];
|
| 143 |
-
if (keys.length) dropped.push({ kind: str(x.kind), keys });
|
| 144 |
-
});
|
| 145 |
-
return {
|
| 146 |
-
draft: {
|
| 147 |
-
name: str(d.name) || "New automation",
|
| 148 |
-
trigger: str(d.trigger) || "manual",
|
| 149 |
-
table: str(d.table),
|
| 150 |
-
actions,
|
| 151 |
-
},
|
| 152 |
-
provider: str(r.provider),
|
| 153 |
-
dropped,
|
| 154 |
-
notes: Array.isArray(r.notes) ? (r.notes as unknown[]).map(str).filter(Boolean) : [],
|
| 155 |
-
// ⚠ `=== true`, never truthy. An absent key must read as "nothing was saved", which is the
|
| 156 |
-
// safe direction: the wrong one would tell a person their flow exists when it does not.
|
| 157 |
-
saved: r.saved === true,
|
| 158 |
-
};
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
/**
|
| 162 |
-
* Describe an automation; get a draft back. **This writes nothing.**
|
| 163 |
-
*
|
| 164 |
-
* The draft is for a person to READ before `acceptDraft` stores it — the only check on the one
|
| 165 |
-
* failure class no validator can see: a well-formed flow that does something other than what was
|
| 166 |
-
* asked for.
|
| 167 |
-
*/
|
| 168 |
-
export function draftFlow(prompt: string): Promise<Result<DraftResult>> {
|
| 169 |
-
return call("/automations/draft", json({ prompt }), parseDraft);
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
/**
|
| 173 |
-
* Store an accepted draft — through the ORDINARY create door, with no marker on it.
|
| 174 |
-
*
|
| 175 |
-
* ⛔ THIS IS THE WHOLE OF R3's *"indistinguishable from a hand-built one"*. There is no
|
| 176 |
-
* `source: "ai"` field and no separate table: what lands is a `plain` automation whose flow
|
| 177 |
-
* happens to have been typed by a model, and every editor, gate and runner treats it as one
|
| 178 |
-
* because it IS one.
|
| 179 |
-
*/
|
| 180 |
-
export function acceptDraft(draft: Draft): Promise<Result<{ id: string }>> {
|
| 181 |
-
return call(
|
| 182 |
-
"/automations",
|
| 183 |
-
json({
|
| 184 |
-
name: draft.name,
|
| 185 |
-
kind: "plain",
|
| 186 |
-
trigger: { key: draft.trigger || "manual" },
|
| 187 |
-
...(draft.table ? { config: { targetTable: draft.table } } : {}),
|
| 188 |
-
flow: {
|
| 189 |
-
actions: draft.actions.map((a) => ({ kind: a.kind, config: a.config })),
|
| 190 |
-
},
|
| 191 |
-
}),
|
| 192 |
-
(b) => {
|
| 193 |
-
const a = ((b || {}) as { automation?: Record<string, unknown> }).automation || {};
|
| 194 |
-
return { id: str(a.id) };
|
| 195 |
-
}
|
| 196 |
-
);
|
| 197 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|