@tonyclaw/agent-inspector 3.0.25 → 3.0.27
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/.output/cli.js +80 -11
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BnvOPwpe.js → CompareDrawer-BVCAAbbu.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-CYOXB81n.js +4234 -0
- package/.output/public/assets/{ReplayDialog-BJgoQWdW.js → ReplayDialog-DMX23O5G.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CpBZEHnM.js → RequestAnatomy-S17pYDRc.js} +1 -1
- package/.output/public/assets/{ResponseView-CWmyRb2M.js → ResponseView-Cf1XrawD.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BWhC8bwx.js → StreamingChunkSequence-BdOrVlCi.js} +1 -1
- package/.output/public/assets/{_sessionId-Cq-2OqUU.js → _sessionId-BnKGmJrR.js} +1 -1
- package/.output/public/assets/{index-Cf8O0Oos.js → index-9fMLCU4d.js} +1 -1
- package/.output/public/assets/{index-BKi9IRtV.js → index-B1atk19i.js} +3 -3
- package/.output/public/assets/index-CmeF5XXS.css +1 -0
- package/.output/public/assets/{index-DdgTJ4QJ.js → index-gY3I-7-D.js} +1 -1
- package/.output/public/assets/{json-viewer-Bqu_4hmm.js → json-viewer-CpyYpPjx.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +6 -6
- package/.output/server/_libs/three.mjs +26016 -0
- package/.output/server/{_sessionId-Ba7H3Gjw.mjs → _sessionId-eA_h2_s6.mjs} +9 -6
- package/.output/server/_ssr/{CompareDrawer-aH59Ab6x.mjs → CompareDrawer-K2k9G7r1.mjs} +10 -7
- package/.output/server/_ssr/{ProxyViewerContainer-By-I0tJv.mjs → ProxyViewerContainer-CbyBpCAj.mjs} +804 -363
- package/.output/server/_ssr/{ReplayDialog-BO4Vy0et.mjs → ReplayDialog-DacI2Iv0.mjs} +10 -7
- package/.output/server/_ssr/{RequestAnatomy-FtOr7vSS.mjs → RequestAnatomy-gh-pAdkO.mjs} +9 -6
- package/.output/server/_ssr/{ResponseView-cvw6ajue.mjs → ResponseView-BRJKN50-.mjs} +10 -7
- package/.output/server/_ssr/{StreamingChunkSequence-Oc8S2kD6.mjs → StreamingChunkSequence-BQLVEuWY.mjs} +9 -6
- package/.output/server/_ssr/{index-BDRlQheR.mjs → index-DuSWrjbH.mjs} +9 -6
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Cd6trhQr.mjs → json-viewer-DNs1ZjKI.mjs} +9 -6
- package/.output/server/_ssr/{router-bofP-Ekl.mjs → router-Dgx6-ot3.mjs} +713 -218
- package/.output/server/{_tanstack-start-manifest_v-BPuf86qB.mjs → _tanstack-start-manifest_v-CoGjnkQD.mjs} +1 -1
- package/.output/server/index.mjs +65 -65
- package/README.md +8 -0
- package/package.json +6 -5
- package/src/cli.ts +43 -9
- package/src/components/inspector-pet/InspectorPet.tsx +66 -51
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +455 -0
- package/src/components/inspector-pet/inspectorPetLogic.ts +28 -43
- package/src/components/providers/ProviderCard.tsx +3 -36
- package/src/components/providers/ProviderForm.tsx +251 -175
- package/src/components/providers/ProvidersPanel.tsx +18 -8
- package/src/components/providers/SettingsDialog.tsx +3 -3
- package/src/components/providers/providerFormCapabilities.ts +36 -0
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
- package/src/contracts/anthropic.ts +0 -5
- package/src/contracts/index.ts +0 -4
- package/src/contracts/log.ts +0 -1
- package/src/contracts/openai.ts +0 -3
- package/src/knowledge/openclawClient.ts +22 -9
- package/src/knowledge/openclawGatewayClient.ts +20 -13
- package/src/lib/alertContract.ts +0 -3
- package/src/lib/ecosystemContract.ts +0 -3
- package/src/lib/export-logs.ts +1 -4
- package/src/lib/safeFetch.ts +100 -0
- package/src/lib/ssrfGuard.ts +133 -0
- package/src/lib/stopReason.ts +1 -4
- package/src/lib/unknown.ts +27 -0
- package/src/lib/useProviders.ts +1 -1
- package/src/mcp/mode.ts +3 -9
- package/src/mcp/server.ts +5 -14
- package/src/mcp/toolHandlers.ts +45 -54
- package/src/proxy/constants.ts +17 -0
- package/src/proxy/formats/anthropic/handler.ts +5 -1
- package/src/proxy/formats/anthropic/schemas.ts +1 -7
- package/src/proxy/formats/index.ts +1 -1
- package/src/proxy/formats/openai/schemas.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -3
- package/src/proxy/handler.ts +91 -12
- package/src/proxy/identityProxy.ts +74 -1
- package/src/proxy/logImporter.ts +82 -12
- package/src/proxy/logIndex.ts +1 -4
- package/src/proxy/providerSecretStore.ts +181 -0
- package/src/proxy/providers.ts +84 -11
- package/src/proxy/schemas.ts +1 -8
- package/src/proxy/sqliteLogIndex.ts +0 -16
- package/src/proxy/store.ts +1 -5
- package/src/proxy/toolSchemaWarnings.ts +1 -9
- package/src/routes/api/logs.$id.replay.ts +17 -5
- package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
- package/src/routes/api/providers.$providerId.ts +18 -5
- package/src/routes/api/providers.ts +18 -7
- package/styles/globals.css +0 -61
- package/.output/public/assets/ProxyViewerContainer-DcqFqo1m.js +0 -127
- package/.output/public/assets/index-BMYuDL5X.css +0 -1
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { type JSX, useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ACESFilmicToneMapping,
|
|
4
|
+
AmbientLight,
|
|
5
|
+
type BufferGeometry,
|
|
6
|
+
CircleGeometry,
|
|
7
|
+
Color,
|
|
8
|
+
CylinderGeometry,
|
|
9
|
+
DirectionalLight,
|
|
10
|
+
Group,
|
|
11
|
+
HemisphereLight,
|
|
12
|
+
type Material,
|
|
13
|
+
Mesh,
|
|
14
|
+
MeshBasicMaterial,
|
|
15
|
+
MeshPhysicalMaterial,
|
|
16
|
+
type Object3D,
|
|
17
|
+
OrthographicCamera,
|
|
18
|
+
PointLight,
|
|
19
|
+
Scene,
|
|
20
|
+
SphereGeometry,
|
|
21
|
+
SRGBColorSpace,
|
|
22
|
+
Vector3,
|
|
23
|
+
WebGLRenderer,
|
|
24
|
+
} from "three";
|
|
25
|
+
import { CrabLogo } from "../ui/crab-logo";
|
|
26
|
+
import type { InspectorPetMood } from "./inspectorPetLogic";
|
|
27
|
+
|
|
28
|
+
type InspectorPetCrab3DProps = {
|
|
29
|
+
mood: InspectorPetMood;
|
|
30
|
+
moving: boolean;
|
|
31
|
+
reduceMotion: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type AnimatedLeg = {
|
|
35
|
+
group: Group;
|
|
36
|
+
phase: number;
|
|
37
|
+
side: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type AnimatedEye = {
|
|
41
|
+
group: Group;
|
|
42
|
+
phase: number;
|
|
43
|
+
pupil: Mesh;
|
|
44
|
+
pupilRest: Vector3;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type AnimatedPincer = {
|
|
48
|
+
direction: number;
|
|
49
|
+
mesh: Mesh;
|
|
50
|
+
restRotationZ: number;
|
|
51
|
+
restY: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type AnimatedClaw = {
|
|
55
|
+
group: Group;
|
|
56
|
+
pincers: AnimatedPincer[];
|
|
57
|
+
scale: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type CrabSceneParts = {
|
|
61
|
+
bodyMaterial: MeshPhysicalMaterial;
|
|
62
|
+
dominantClaw: AnimatedClaw;
|
|
63
|
+
eyes: AnimatedEye[];
|
|
64
|
+
legs: AnimatedLeg[];
|
|
65
|
+
rimLight: PointLight;
|
|
66
|
+
root: Group;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function createRod(
|
|
70
|
+
start: Vector3,
|
|
71
|
+
end: Vector3,
|
|
72
|
+
radius: number,
|
|
73
|
+
material: MeshPhysicalMaterial,
|
|
74
|
+
): Mesh {
|
|
75
|
+
const direction = end.clone().sub(start);
|
|
76
|
+
const length = direction.length();
|
|
77
|
+
const rod = new Mesh(new CylinderGeometry(radius, radius * 0.9, length, 12), material);
|
|
78
|
+
rod.position.copy(start.clone().add(end).multiplyScalar(0.5));
|
|
79
|
+
rod.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), direction.normalize());
|
|
80
|
+
return rod;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function addClaw(
|
|
84
|
+
root: Group,
|
|
85
|
+
side: number,
|
|
86
|
+
shellMaterial: MeshPhysicalMaterial,
|
|
87
|
+
accentMaterial: MeshPhysicalMaterial,
|
|
88
|
+
): AnimatedClaw {
|
|
89
|
+
const clawScale = side < 0 ? 1.5 : 0.68;
|
|
90
|
+
const clawGroup = new Group();
|
|
91
|
+
const pincers: AnimatedPincer[] = [];
|
|
92
|
+
root.add(clawGroup);
|
|
93
|
+
|
|
94
|
+
const shoulder = new Vector3(side * 1.2, 0.25, -0.05);
|
|
95
|
+
const wrist = new Vector3(side * (1.65 + clawScale * 0.18), 0.5, 0);
|
|
96
|
+
const arm = createRod(shoulder, wrist, 0.16 * clawScale, accentMaterial);
|
|
97
|
+
clawGroup.add(arm);
|
|
98
|
+
|
|
99
|
+
const palm = new Mesh(new SphereGeometry(0.55, 24, 16), shellMaterial);
|
|
100
|
+
palm.position.set(side * (1.95 + clawScale * 0.16), 0.62, 0.05);
|
|
101
|
+
palm.scale.set(1.05 * clawScale, 0.72 * clawScale, 0.55 * clawScale);
|
|
102
|
+
clawGroup.add(palm);
|
|
103
|
+
|
|
104
|
+
for (const pincerDirection of [-1, 1]) {
|
|
105
|
+
const pincer = new Mesh(new SphereGeometry(0.36, 20, 14), accentMaterial);
|
|
106
|
+
pincer.position.set(
|
|
107
|
+
side * (2.22 + clawScale * 0.26),
|
|
108
|
+
0.62 + pincerDirection * 0.24 * clawScale,
|
|
109
|
+
0.08,
|
|
110
|
+
);
|
|
111
|
+
pincer.scale.set(1.05 * clawScale, 0.34 * clawScale, 0.42 * clawScale);
|
|
112
|
+
pincer.rotation.z = pincerDirection * side * 0.42;
|
|
113
|
+
clawGroup.add(pincer);
|
|
114
|
+
pincers.push({
|
|
115
|
+
direction: pincerDirection,
|
|
116
|
+
mesh: pincer,
|
|
117
|
+
restRotationZ: pincer.rotation.z,
|
|
118
|
+
restY: pincer.position.y,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { group: clawGroup, pincers, scale: clawScale };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function pinchPulse(cycleTime: number, start: number, duration: number): number {
|
|
126
|
+
const progress = (cycleTime - start) / duration;
|
|
127
|
+
if (progress <= 0 || progress >= 1) return 0;
|
|
128
|
+
return Math.sin(progress * Math.PI);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function dominantClawGrip(elapsed: number): number {
|
|
132
|
+
const cycleTime = elapsed % 6.8;
|
|
133
|
+
return Math.max(pinchPulse(cycleTime, 0.72, 0.82), pinchPulse(cycleTime, 1.72, 0.68));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function addEyes(
|
|
137
|
+
root: Group,
|
|
138
|
+
shellMaterial: MeshPhysicalMaterial,
|
|
139
|
+
eyeMaterial: MeshPhysicalMaterial,
|
|
140
|
+
): AnimatedEye[] {
|
|
141
|
+
const eyes: AnimatedEye[] = [];
|
|
142
|
+
for (const side of [-1, 1]) {
|
|
143
|
+
const stalkStart = new Vector3(side * 0.48, 0.68, 0.05);
|
|
144
|
+
const stalkEnd = new Vector3(side * 0.1, 0.82, 0.07);
|
|
145
|
+
const eyeGroup = new Group();
|
|
146
|
+
eyeGroup.position.copy(stalkStart);
|
|
147
|
+
eyeGroup.add(createRod(new Vector3(0, 0, 0), stalkEnd, 0.1, shellMaterial));
|
|
148
|
+
|
|
149
|
+
const eye = new Mesh(new SphereGeometry(0.24, 20, 16), eyeMaterial);
|
|
150
|
+
eye.position.copy(stalkEnd);
|
|
151
|
+
eye.scale.set(1, 1, 0.85);
|
|
152
|
+
eyeGroup.add(eye);
|
|
153
|
+
|
|
154
|
+
const pupil = new Mesh(
|
|
155
|
+
new SphereGeometry(0.105, 16, 12),
|
|
156
|
+
new MeshPhysicalMaterial({ color: 0x111827, roughness: 0.25 }),
|
|
157
|
+
);
|
|
158
|
+
const pupilRest = new Vector3(stalkEnd.x, stalkEnd.y, stalkEnd.z + 0.2);
|
|
159
|
+
pupil.position.copy(pupilRest);
|
|
160
|
+
eyeGroup.add(pupil);
|
|
161
|
+
root.add(eyeGroup);
|
|
162
|
+
eyes.push({
|
|
163
|
+
group: eyeGroup,
|
|
164
|
+
phase: side < 0 ? 0 : Math.PI / 3,
|
|
165
|
+
pupil,
|
|
166
|
+
pupilRest,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return eyes;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function addLegs(root: Group, legMaterial: MeshPhysicalMaterial): AnimatedLeg[] {
|
|
173
|
+
const legs: AnimatedLeg[] = [];
|
|
174
|
+
for (const side of [-1, 1]) {
|
|
175
|
+
for (let index = 0; index < 4; index += 1) {
|
|
176
|
+
const leg = new Group();
|
|
177
|
+
const baseY = -0.3 - index * 0.22;
|
|
178
|
+
leg.position.set(side * (0.8 + index * 0.12), baseY, -0.18);
|
|
179
|
+
leg.add(
|
|
180
|
+
createRod(
|
|
181
|
+
new Vector3(0, 0, 0),
|
|
182
|
+
new Vector3(side * (0.62 + index * 0.06), -0.34, 0),
|
|
183
|
+
0.095,
|
|
184
|
+
legMaterial,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
leg.add(
|
|
188
|
+
createRod(
|
|
189
|
+
new Vector3(side * (0.58 + index * 0.06), -0.31, 0),
|
|
190
|
+
new Vector3(side * (1.02 + index * 0.08), -0.88 + index * 0.07, 0.04),
|
|
191
|
+
0.075,
|
|
192
|
+
legMaterial,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
root.add(leg);
|
|
196
|
+
legs.push({ group: leg, phase: index * Math.PI + (side > 0 ? Math.PI / 2 : 0), side });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return legs;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function createCrabScene(scene: Scene): CrabSceneParts {
|
|
203
|
+
const shellMaterial = new MeshPhysicalMaterial({
|
|
204
|
+
color: 0xf59e0b,
|
|
205
|
+
roughness: 0.34,
|
|
206
|
+
metalness: 0.04,
|
|
207
|
+
clearcoat: 0.78,
|
|
208
|
+
clearcoatRoughness: 0.18,
|
|
209
|
+
emissive: new Color(0x000000),
|
|
210
|
+
emissiveIntensity: 0,
|
|
211
|
+
});
|
|
212
|
+
const accentMaterial = new MeshPhysicalMaterial({
|
|
213
|
+
color: 0xfb923c,
|
|
214
|
+
roughness: 0.42,
|
|
215
|
+
metalness: 0.02,
|
|
216
|
+
clearcoat: 0.52,
|
|
217
|
+
clearcoatRoughness: 0.28,
|
|
218
|
+
});
|
|
219
|
+
const legMaterial = new MeshPhysicalMaterial({
|
|
220
|
+
color: 0xd97706,
|
|
221
|
+
roughness: 0.48,
|
|
222
|
+
clearcoat: 0.35,
|
|
223
|
+
clearcoatRoughness: 0.35,
|
|
224
|
+
});
|
|
225
|
+
const eyeMaterial = new MeshPhysicalMaterial({
|
|
226
|
+
color: 0xfffbeb,
|
|
227
|
+
roughness: 0.22,
|
|
228
|
+
clearcoat: 0.8,
|
|
229
|
+
clearcoatRoughness: 0.12,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const root = new Group();
|
|
233
|
+
root.rotation.x = -0.12;
|
|
234
|
+
root.rotation.y = -0.08;
|
|
235
|
+
root.scale.setScalar(1.08);
|
|
236
|
+
scene.add(root);
|
|
237
|
+
|
|
238
|
+
const shadow = new Mesh(
|
|
239
|
+
new CircleGeometry(1.65, 40),
|
|
240
|
+
new MeshBasicMaterial({ color: 0x111827, transparent: true, opacity: 0.16, depthWrite: false }),
|
|
241
|
+
);
|
|
242
|
+
shadow.position.set(0, -1.33, -0.65);
|
|
243
|
+
shadow.scale.set(1.4, 0.34, 1);
|
|
244
|
+
root.add(shadow);
|
|
245
|
+
|
|
246
|
+
const body = new Mesh(new SphereGeometry(1, 36, 24), shellMaterial);
|
|
247
|
+
body.position.set(0, 0.02, 0);
|
|
248
|
+
body.scale.set(1.58, 0.92, 0.68);
|
|
249
|
+
root.add(body);
|
|
250
|
+
|
|
251
|
+
const lowerShell = new Mesh(new SphereGeometry(0.92, 28, 20), accentMaterial);
|
|
252
|
+
lowerShell.position.set(0, -0.48, 0.22);
|
|
253
|
+
lowerShell.scale.set(1.28, 0.42, 0.56);
|
|
254
|
+
root.add(lowerShell);
|
|
255
|
+
|
|
256
|
+
const dominantClaw = addClaw(root, -1, shellMaterial, accentMaterial);
|
|
257
|
+
addClaw(root, 1, shellMaterial, accentMaterial);
|
|
258
|
+
const eyes = addEyes(root, shellMaterial, eyeMaterial);
|
|
259
|
+
const legs = addLegs(root, legMaterial);
|
|
260
|
+
|
|
261
|
+
scene.add(new AmbientLight(0xfff7ed, 1.65));
|
|
262
|
+
scene.add(new HemisphereLight(0xfff7ed, 0x172033, 2.15));
|
|
263
|
+
const keyLight = new DirectionalLight(0xffffff, 3.6);
|
|
264
|
+
keyLight.position.set(3.5, 4.5, 6);
|
|
265
|
+
scene.add(keyLight);
|
|
266
|
+
const fillLight = new DirectionalLight(0xfbbf24, 1.35);
|
|
267
|
+
fillLight.position.set(-4, 1.5, 4);
|
|
268
|
+
scene.add(fillLight);
|
|
269
|
+
const rimLight = new PointLight(0xf59e0b, 1.8, 12, 2);
|
|
270
|
+
rimLight.position.set(0, 1.8, 4.5);
|
|
271
|
+
scene.add(rimLight);
|
|
272
|
+
|
|
273
|
+
return { bodyMaterial: shellMaterial, dominantClaw, eyes, legs, rimLight, root };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function moodColor(mood: InspectorPetMood): number {
|
|
277
|
+
switch (mood) {
|
|
278
|
+
case "active":
|
|
279
|
+
return 0x22d3ee;
|
|
280
|
+
case "attention":
|
|
281
|
+
return 0xef4444;
|
|
282
|
+
case "idle":
|
|
283
|
+
return 0xf59e0b;
|
|
284
|
+
case "sleeping":
|
|
285
|
+
return 0x94a3b8;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isDisposableMesh(object: Object3D): object is Mesh<BufferGeometry, Material | Material[]> {
|
|
290
|
+
return object instanceof Mesh;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function disposeScene(scene: Scene): void {
|
|
294
|
+
const materials = new Set<Material>();
|
|
295
|
+
scene.traverse((object: Object3D) => {
|
|
296
|
+
if (!isDisposableMesh(object)) return;
|
|
297
|
+
object.geometry.dispose();
|
|
298
|
+
const material = object.material;
|
|
299
|
+
if (Array.isArray(material)) {
|
|
300
|
+
for (const item of material) materials.add(item);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
materials.add(material);
|
|
304
|
+
});
|
|
305
|
+
for (const material of materials) material.dispose();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function InspectorPetCrab3D({
|
|
309
|
+
mood,
|
|
310
|
+
moving,
|
|
311
|
+
reduceMotion,
|
|
312
|
+
}: InspectorPetCrab3DProps): JSX.Element {
|
|
313
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
314
|
+
const moodRef = useRef(mood);
|
|
315
|
+
const movingRef = useRef(moving);
|
|
316
|
+
const reduceMotionRef = useRef(reduceMotion);
|
|
317
|
+
const [renderFailed, setRenderFailed] = useState(false);
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
moodRef.current = mood;
|
|
321
|
+
}, [mood]);
|
|
322
|
+
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
movingRef.current = moving;
|
|
325
|
+
}, [moving]);
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
reduceMotionRef.current = reduceMotion;
|
|
329
|
+
}, [reduceMotion]);
|
|
330
|
+
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
const canvas = canvasRef.current;
|
|
333
|
+
if (canvas === null) return undefined;
|
|
334
|
+
|
|
335
|
+
let renderer: WebGLRenderer;
|
|
336
|
+
try {
|
|
337
|
+
renderer = new WebGLRenderer({
|
|
338
|
+
alpha: true,
|
|
339
|
+
antialias: true,
|
|
340
|
+
canvas,
|
|
341
|
+
powerPreference: "low-power",
|
|
342
|
+
preserveDrawingBuffer: true,
|
|
343
|
+
});
|
|
344
|
+
} catch {
|
|
345
|
+
setRenderFailed(true);
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
renderer.setClearColor(0x000000, 0);
|
|
350
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
351
|
+
renderer.toneMapping = ACESFilmicToneMapping;
|
|
352
|
+
renderer.toneMappingExposure = 1.18;
|
|
353
|
+
|
|
354
|
+
const scene = new Scene();
|
|
355
|
+
const camera = new OrthographicCamera(-3.65, 3.65, 3.65, -3.65, 0.1, 20);
|
|
356
|
+
camera.position.set(0, 0.05, 8);
|
|
357
|
+
camera.lookAt(0, 0, 0);
|
|
358
|
+
const parts = createCrabScene(scene);
|
|
359
|
+
const startedAt = window.performance.now();
|
|
360
|
+
let lastFrameAt = 0;
|
|
361
|
+
|
|
362
|
+
const renderFrame = (timestamp: number): void => {
|
|
363
|
+
if (timestamp - lastFrameAt < 32) return;
|
|
364
|
+
lastFrameAt = timestamp;
|
|
365
|
+
const elapsed = (timestamp - startedAt) / 1000;
|
|
366
|
+
const motionAllowed = !reduceMotionRef.current;
|
|
367
|
+
const gait = movingRef.current ? 1 : moodRef.current === "active" ? 0.42 : 0;
|
|
368
|
+
const idleMotion = motionAllowed ? Math.sin(elapsed * 0.85) : 0;
|
|
369
|
+
const stepMotion = motionAllowed ? Math.sin(elapsed * 3.4) : 0;
|
|
370
|
+
const clawGrip = motionAllowed ? dominantClawGrip(elapsed) : 0;
|
|
371
|
+
|
|
372
|
+
parts.root.position.y = idleMotion * 0.035 + Math.abs(stepMotion) * 0.055 * gait;
|
|
373
|
+
parts.root.rotation.y = -0.08 + idleMotion * 0.055;
|
|
374
|
+
parts.root.rotation.z = stepMotion * 0.028 * gait;
|
|
375
|
+
parts.dominantClaw.group.rotation.z = -clawGrip * 0.035;
|
|
376
|
+
for (const pincer of parts.dominantClaw.pincers) {
|
|
377
|
+
pincer.mesh.position.y =
|
|
378
|
+
pincer.restY - pincer.direction * parts.dominantClaw.scale * 0.16 * clawGrip;
|
|
379
|
+
pincer.mesh.rotation.z = pincer.restRotationZ * (1 - clawGrip * 0.72);
|
|
380
|
+
}
|
|
381
|
+
for (const leg of parts.legs) {
|
|
382
|
+
leg.group.rotation.z =
|
|
383
|
+
motionAllowed && gait > 0
|
|
384
|
+
? Math.sin(elapsed * 4.25 + leg.phase) * 0.2 * leg.side * gait
|
|
385
|
+
: 0;
|
|
386
|
+
leg.group.rotation.x = motionAllowed && gait > 0 ? stepMotion * 0.045 * gait : 0;
|
|
387
|
+
}
|
|
388
|
+
for (const eye of parts.eyes) {
|
|
389
|
+
const gaze = motionAllowed ? Math.sin(elapsed * 0.72 + eye.phase) : 0;
|
|
390
|
+
const lift = motionAllowed ? Math.cos(elapsed * 0.9 + eye.phase) : 0;
|
|
391
|
+
eye.group.rotation.z = gaze * 0.075;
|
|
392
|
+
eye.group.rotation.x = lift * 0.025;
|
|
393
|
+
eye.pupil.position.set(
|
|
394
|
+
eye.pupilRest.x + gaze * 0.055,
|
|
395
|
+
eye.pupilRest.y + lift * 0.025,
|
|
396
|
+
eye.pupilRest.z,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const color = moodColor(moodRef.current);
|
|
401
|
+
parts.rimLight.color.setHex(color);
|
|
402
|
+
parts.rimLight.intensity = moodRef.current === "attention" ? 3.2 : 1.8;
|
|
403
|
+
parts.bodyMaterial.emissive.setHex(color);
|
|
404
|
+
parts.bodyMaterial.emissiveIntensity = moodRef.current === "active" ? 0.12 : 0.035;
|
|
405
|
+
renderer.render(scene, camera);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const resize = (): void => {
|
|
409
|
+
const width = Math.max(canvas.clientWidth, 1);
|
|
410
|
+
const height = Math.max(canvas.clientHeight, 1);
|
|
411
|
+
renderer.setDrawingBufferSize(width, height, Math.min(window.devicePixelRatio, 2));
|
|
412
|
+
renderer.render(scene, camera);
|
|
413
|
+
};
|
|
414
|
+
const resizeObserver =
|
|
415
|
+
typeof ResizeObserver === "undefined" ? null : new ResizeObserver(resize);
|
|
416
|
+
if (resizeObserver === null) {
|
|
417
|
+
window.addEventListener("resize", resize);
|
|
418
|
+
} else {
|
|
419
|
+
resizeObserver.observe(canvas);
|
|
420
|
+
}
|
|
421
|
+
resize();
|
|
422
|
+
|
|
423
|
+
const handleContextLost = (event: Event): void => {
|
|
424
|
+
event.preventDefault();
|
|
425
|
+
setRenderFailed(true);
|
|
426
|
+
};
|
|
427
|
+
canvas.addEventListener("webglcontextlost", handleContextLost);
|
|
428
|
+
renderer.setAnimationLoop(renderFrame);
|
|
429
|
+
|
|
430
|
+
return () => {
|
|
431
|
+
renderer.setAnimationLoop(null);
|
|
432
|
+
if (resizeObserver === null) {
|
|
433
|
+
window.removeEventListener("resize", resize);
|
|
434
|
+
} else {
|
|
435
|
+
resizeObserver.disconnect();
|
|
436
|
+
}
|
|
437
|
+
canvas.removeEventListener("webglcontextlost", handleContextLost);
|
|
438
|
+
disposeScene(scene);
|
|
439
|
+
renderer.dispose();
|
|
440
|
+
};
|
|
441
|
+
}, []);
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<span className="relative flex size-24 items-center justify-center" aria-hidden="true">
|
|
445
|
+
<canvas
|
|
446
|
+
ref={canvasRef}
|
|
447
|
+
className={renderFailed ? "hidden" : "block size-full bg-transparent"}
|
|
448
|
+
data-inspector-pet-canvas="true"
|
|
449
|
+
/>
|
|
450
|
+
{renderFailed && (
|
|
451
|
+
<CrabLogo className="size-22 text-amber-500 drop-shadow-[0_0_18px_rgba(245,158,11,0.28)]" />
|
|
452
|
+
)}
|
|
453
|
+
</span>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
@@ -21,11 +21,11 @@ export type InspectorPetStatus = {
|
|
|
21
21
|
capturedCount: number;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export const INSPECTOR_PET_SIZE =
|
|
24
|
+
export const INSPECTOR_PET_SIZE = 112;
|
|
25
25
|
export const INSPECTOR_PET_MARGIN = 14;
|
|
26
|
-
export const
|
|
27
|
-
export const INSPECTOR_PET_MIN_STEP =
|
|
28
|
-
export const INSPECTOR_PET_MAX_STEP =
|
|
26
|
+
export const INSPECTOR_PET_EDGE_GAP = 20;
|
|
27
|
+
export const INSPECTOR_PET_MIN_STEP = 36;
|
|
28
|
+
export const INSPECTOR_PET_MAX_STEP = 92;
|
|
29
29
|
export const INSPECTOR_PET_SLEEP_AFTER_MS = 45_000;
|
|
30
30
|
|
|
31
31
|
const INSPECTOR_PET_PANEL_WIDTH = 192;
|
|
@@ -47,29 +47,23 @@ function clampUnit(value: number): number {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function maximumPetX(viewport: InspectorPetViewport): number {
|
|
50
|
-
const
|
|
51
|
-
return Math.max(
|
|
52
|
-
-edgePeek,
|
|
53
|
-
viewport.width - scaledMetric(INSPECTOR_PET_SIZE, viewport) + edgePeek,
|
|
54
|
-
);
|
|
50
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
51
|
+
return Math.max(edgeGap, viewport.width - scaledMetric(INSPECTOR_PET_SIZE, viewport) - edgeGap);
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
function maximumPetY(viewport: InspectorPetViewport): number {
|
|
58
|
-
const
|
|
59
|
-
return Math.max(
|
|
60
|
-
-edgePeek,
|
|
61
|
-
viewport.height - scaledMetric(INSPECTOR_PET_SIZE, viewport) + edgePeek,
|
|
62
|
-
);
|
|
55
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
56
|
+
return Math.max(edgeGap, viewport.height - scaledMetric(INSPECTOR_PET_SIZE, viewport) - edgeGap);
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
export function clampInspectorPetPosition(
|
|
66
60
|
position: InspectorPetPosition,
|
|
67
61
|
viewport: InspectorPetViewport,
|
|
68
62
|
): InspectorPetPosition {
|
|
69
|
-
const
|
|
63
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
70
64
|
return {
|
|
71
|
-
x: Math.min(Math.max(position.x,
|
|
72
|
-
y: Math.min(Math.max(position.y,
|
|
65
|
+
x: Math.min(Math.max(position.x, edgeGap), maximumPetX(viewport)),
|
|
66
|
+
y: Math.min(Math.max(position.y, edgeGap), maximumPetY(viewport)),
|
|
73
67
|
};
|
|
74
68
|
}
|
|
75
69
|
|
|
@@ -78,11 +72,11 @@ export function nearestInspectorPetEdge(
|
|
|
78
72
|
viewport: InspectorPetViewport,
|
|
79
73
|
): InspectorPetEdge {
|
|
80
74
|
const clamped = clampInspectorPetPosition(position, viewport);
|
|
81
|
-
const
|
|
75
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
82
76
|
const distances: { edge: InspectorPetEdge; distance: number }[] = [
|
|
83
|
-
{ edge: "left", distance: clamped.x
|
|
77
|
+
{ edge: "left", distance: clamped.x - edgeGap },
|
|
84
78
|
{ edge: "right", distance: maximumPetX(viewport) - clamped.x },
|
|
85
|
-
{ edge: "top", distance: clamped.y
|
|
79
|
+
{ edge: "top", distance: clamped.y - edgeGap },
|
|
86
80
|
{ edge: "bottom", distance: maximumPetY(viewport) - clamped.y },
|
|
87
81
|
];
|
|
88
82
|
return distances.reduce((best, current) => (current.distance < best.distance ? current : best))
|
|
@@ -95,15 +89,15 @@ export function snapInspectorPetToEdge(
|
|
|
95
89
|
): InspectorPetPosition {
|
|
96
90
|
const clamped = clampInspectorPetPosition(position, viewport);
|
|
97
91
|
const edge = nearestInspectorPetEdge(clamped, viewport);
|
|
98
|
-
const
|
|
92
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
99
93
|
|
|
100
94
|
switch (edge) {
|
|
101
95
|
case "left":
|
|
102
|
-
return { x:
|
|
96
|
+
return { x: edgeGap, y: clamped.y };
|
|
103
97
|
case "right":
|
|
104
98
|
return { x: maximumPetX(viewport), y: clamped.y };
|
|
105
99
|
case "top":
|
|
106
|
-
return { x: clamped.x, y:
|
|
100
|
+
return { x: clamped.x, y: edgeGap };
|
|
107
101
|
case "bottom":
|
|
108
102
|
return { x: clamped.x, y: maximumPetY(viewport) };
|
|
109
103
|
}
|
|
@@ -111,24 +105,15 @@ export function snapInspectorPetToEdge(
|
|
|
111
105
|
|
|
112
106
|
export function randomInspectorPetEdgePosition(
|
|
113
107
|
viewport: InspectorPetViewport,
|
|
114
|
-
|
|
115
|
-
ySample: number,
|
|
108
|
+
positionSample: number,
|
|
116
109
|
): InspectorPetPosition {
|
|
117
110
|
const maxX = maximumPetX(viewport);
|
|
118
111
|
const maxY = maximumPetY(viewport);
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
switch (edgeIndex) {
|
|
125
|
-
case 0:
|
|
126
|
-
return { x: -edgePeek, y: randomY };
|
|
127
|
-
case 1:
|
|
128
|
-
return { x: maxX, y: randomY };
|
|
129
|
-
default:
|
|
130
|
-
return { x: maxX, y: maxY };
|
|
131
|
-
}
|
|
112
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
113
|
+
return {
|
|
114
|
+
x: edgeGap + clampUnit(positionSample) * (maxX - edgeGap),
|
|
115
|
+
y: maxY,
|
|
116
|
+
};
|
|
132
117
|
}
|
|
133
118
|
|
|
134
119
|
export function nextInspectorPetWalkingPosition(
|
|
@@ -145,13 +130,13 @@ export function nextInspectorPetWalkingPosition(
|
|
|
145
130
|
const edge = nearestInspectorPetEdge(clamped, viewport);
|
|
146
131
|
const maxX = maximumPetX(viewport);
|
|
147
132
|
const maxY = maximumPetY(viewport);
|
|
148
|
-
const
|
|
133
|
+
const edgeGap = scaledMetric(INSPECTOR_PET_EDGE_GAP, viewport);
|
|
149
134
|
const minimumSafeY = Math.min(scaledMetric(INSPECTOR_PET_VERTICAL_SAFE_MARGIN, viewport), maxY);
|
|
150
135
|
|
|
151
136
|
switch (edge) {
|
|
152
137
|
case "left":
|
|
153
138
|
return {
|
|
154
|
-
x:
|
|
139
|
+
x: edgeGap,
|
|
155
140
|
y: Math.min(Math.max(clamped.y + step * direction, minimumSafeY), maxY),
|
|
156
141
|
};
|
|
157
142
|
case "right":
|
|
@@ -161,12 +146,12 @@ export function nextInspectorPetWalkingPosition(
|
|
|
161
146
|
};
|
|
162
147
|
case "top":
|
|
163
148
|
return {
|
|
164
|
-
x: Math.min(Math.max(clamped.x + step * direction,
|
|
165
|
-
y:
|
|
149
|
+
x: Math.min(Math.max(clamped.x + step * direction, edgeGap), maxX),
|
|
150
|
+
y: edgeGap,
|
|
166
151
|
};
|
|
167
152
|
case "bottom":
|
|
168
153
|
return {
|
|
169
|
-
x: Math.min(Math.max(clamped.x + step * direction,
|
|
154
|
+
x: Math.min(Math.max(clamped.x + step * direction, edgeGap), maxX),
|
|
170
155
|
y: maxY,
|
|
171
156
|
};
|
|
172
157
|
}
|
|
@@ -2,22 +2,16 @@ import { type JSX, useState } from "react";
|
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
4
4
|
import {
|
|
5
|
-
Eye,
|
|
6
|
-
EyeOff,
|
|
7
5
|
Pencil,
|
|
8
6
|
Trash2,
|
|
9
7
|
RotateCw,
|
|
10
8
|
CheckCircle,
|
|
11
9
|
ExternalLink,
|
|
12
10
|
Clock,
|
|
13
|
-
Copy,
|
|
14
|
-
Check,
|
|
15
11
|
ChevronDown,
|
|
16
12
|
ChevronRight,
|
|
17
13
|
} from "lucide-react";
|
|
18
14
|
import type { ProviderConfig } from "../../proxy/providers";
|
|
19
|
-
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
20
|
-
import { maskApiKey } from "../../lib/mask";
|
|
21
15
|
import {
|
|
22
16
|
findProviderModelMetadata,
|
|
23
17
|
providerHasContextMetadata,
|
|
@@ -174,18 +168,8 @@ export function ProviderCard({
|
|
|
174
168
|
onRefreshMetadata,
|
|
175
169
|
isRefreshingMetadata = false,
|
|
176
170
|
}: ProviderCardProps): JSX.Element {
|
|
177
|
-
const [showApiKey, setShowApiKey] = useState(false);
|
|
178
|
-
const [copied, setCopied] = useState(false);
|
|
179
171
|
const [showDetails, setShowDetails] = useState(false);
|
|
180
172
|
|
|
181
|
-
function handleCopy() {
|
|
182
|
-
void copyTextToClipboard(provider.apiKey).then((success) => {
|
|
183
|
-
if (!success) return;
|
|
184
|
-
setCopied(true);
|
|
185
|
-
setTimeout(() => setCopied(false), 2000);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
173
|
const docsUrl =
|
|
190
174
|
provider.apiDocsUrl ??
|
|
191
175
|
Object.entries(KNOWN_PROVIDER_DOCS).find(([keyword]) =>
|
|
@@ -285,26 +269,9 @@ export function ProviderCard({
|
|
|
285
269
|
|
|
286
270
|
{showDetails && (
|
|
287
271
|
<div className="space-y-3 border-t border-border/60 pt-3">
|
|
288
|
-
<div className="flex items-center gap-2">
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
</code>
|
|
292
|
-
<button
|
|
293
|
-
type="button"
|
|
294
|
-
onClick={() => setShowApiKey((s) => !s)}
|
|
295
|
-
className="p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
296
|
-
aria-label={showApiKey ? "Hide API key" : "Show API key"}
|
|
297
|
-
>
|
|
298
|
-
{showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
299
|
-
</button>
|
|
300
|
-
<button
|
|
301
|
-
type="button"
|
|
302
|
-
onClick={handleCopy}
|
|
303
|
-
className="p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
304
|
-
aria-label="Copy API key"
|
|
305
|
-
>
|
|
306
|
-
{copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
|
|
307
|
-
</button>
|
|
272
|
+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
273
|
+
<CheckCircle className="size-3.5 text-emerald-500" />
|
|
274
|
+
<span>API key configured</span>
|
|
308
275
|
</div>
|
|
309
276
|
|
|
310
277
|
{provider.models !== undefined && provider.models.length > 0 && (
|