egaki 0.9.0 → 0.11.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/CHANGELOG.md +72 -0
- package/dist/cli/chatgpt-auth.d.ts +30 -5
- package/dist/cli/chatgpt-auth.d.ts.map +1 -1
- package/dist/cli/chatgpt-auth.js +257 -79
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +132 -28
- package/dist/cli/dev-server.d.ts +30 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +266 -0
- package/dist/cli/dev-server.test.d.ts +2 -0
- package/dist/cli/dev-server.test.d.ts.map +1 -0
- package/dist/cli/dev-server.test.js +95 -0
- package/dist/cli/generate.d.ts +8 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +170 -58
- package/dist/cli/generate.test.js +50 -1
- package/dist/cli/login.d.ts +38 -3
- package/dist/cli/login.d.ts.map +1 -1
- package/dist/cli/login.js +44 -17
- package/dist/cli/login.test.d.ts +2 -0
- package/dist/cli/login.test.d.ts.map +1 -0
- package/dist/cli/login.test.js +158 -0
- package/dist/cli/open-browser.d.ts +3 -1
- package/dist/cli/open-browser.d.ts.map +1 -1
- package/dist/cli/open-browser.js +2 -2
- package/dist/cli/xai-auth.d.ts +3 -1
- package/dist/cli/xai-auth.d.ts.map +1 -1
- package/dist/cli/xai-auth.js +13 -3
- package/dist/vite/angled-screen-shader.d.ts +5 -0
- package/dist/vite/angled-screen-shader.d.ts.map +1 -1
- package/dist/vite/angled-screen-shader.js +77 -34
- package/dist/vite/generated-media-client.d.ts.map +1 -1
- package/dist/vite/generated-media-client.js +2 -2
- package/dist/vite/vite-plugin.d.ts.map +1 -1
- package/dist/vite/vite-plugin.js +128 -10
- package/package.json +6 -10
- package/skills/egaki/SKILL.md +14 -0
- package/src/cli/chatgpt-auth.ts +379 -89
- package/src/cli/cli.ts +161 -27
- package/src/cli/dev-server.test.ts +109 -0
- package/src/cli/dev-server.ts +303 -0
- package/src/cli/generate.test.ts +57 -0
- package/src/cli/generate.ts +191 -61
- package/src/cli/login.test.ts +185 -0
- package/src/cli/login.ts +68 -16
- package/src/cli/open-browser.ts +7 -6
- package/src/cli/xai-auth.ts +15 -3
- package/src/vite/angled-screen-shader.tsx +81 -34
- package/src/vite/generated-media-client.tsx +6 -1
- package/src/vite/vite-plugin.ts +137 -10
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Tests login command routing before any prompt, key read, or OAuth side effect.
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { chatGptReLoginMessage, isRevokedChatGptAccessToken, oauthCallbackAction, pickChatGptResponsesModel, } from './chatgpt-auth.js';
|
|
4
|
+
import { describeChatGptExpiry, resolveLoginAction } from './login.js';
|
|
5
|
+
describe('resolveLoginAction', () => {
|
|
6
|
+
test('shows all configured providers', () => {
|
|
7
|
+
expect(resolveLoginAction({
|
|
8
|
+
options: { show: true },
|
|
9
|
+
daemonProvider: undefined,
|
|
10
|
+
isAgent: false,
|
|
11
|
+
})).toMatchInlineSnapshot(`
|
|
12
|
+
{
|
|
13
|
+
"type": "show",
|
|
14
|
+
}
|
|
15
|
+
`);
|
|
16
|
+
});
|
|
17
|
+
test('removes a configured provider', () => {
|
|
18
|
+
expect(resolveLoginAction({
|
|
19
|
+
options: { remove: 'google' },
|
|
20
|
+
daemonProvider: undefined,
|
|
21
|
+
isAgent: false,
|
|
22
|
+
})).toMatchInlineSnapshot(`
|
|
23
|
+
{
|
|
24
|
+
"provider": "google",
|
|
25
|
+
"type": "remove",
|
|
26
|
+
}
|
|
27
|
+
`);
|
|
28
|
+
});
|
|
29
|
+
test('runs the OAuth provider passed to the daemon', () => {
|
|
30
|
+
expect(resolveLoginAction({
|
|
31
|
+
options: {},
|
|
32
|
+
daemonProvider: 'chatgpt',
|
|
33
|
+
isAgent: true,
|
|
34
|
+
})).toMatchInlineSnapshot(`
|
|
35
|
+
{
|
|
36
|
+
"provider": "chatgpt",
|
|
37
|
+
"type": "oauth-daemon",
|
|
38
|
+
}
|
|
39
|
+
`);
|
|
40
|
+
});
|
|
41
|
+
test('rejects a non-OAuth daemon provider', () => {
|
|
42
|
+
expect(resolveLoginAction({
|
|
43
|
+
options: {},
|
|
44
|
+
daemonProvider: 'google',
|
|
45
|
+
isAgent: true,
|
|
46
|
+
})).toMatchInlineSnapshot(`
|
|
47
|
+
[Error: Invalid background login provider: google]
|
|
48
|
+
`);
|
|
49
|
+
});
|
|
50
|
+
test('starts an explicit OAuth provider through the daemon client', () => {
|
|
51
|
+
expect(resolveLoginAction({
|
|
52
|
+
options: { provider: 'xai-oauth' },
|
|
53
|
+
daemonProvider: undefined,
|
|
54
|
+
isAgent: true,
|
|
55
|
+
})).toMatchInlineSnapshot(`
|
|
56
|
+
{
|
|
57
|
+
"provider": "xai-oauth",
|
|
58
|
+
"type": "oauth-client",
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
});
|
|
62
|
+
test('saves an explicit API key in the foreground', () => {
|
|
63
|
+
expect(resolveLoginAction({
|
|
64
|
+
options: { provider: 'google', key: 'secret' },
|
|
65
|
+
daemonProvider: undefined,
|
|
66
|
+
isAgent: true,
|
|
67
|
+
})).toMatchInlineSnapshot(`
|
|
68
|
+
{
|
|
69
|
+
"key": "secret",
|
|
70
|
+
"provider": "google",
|
|
71
|
+
"type": "api-key",
|
|
72
|
+
}
|
|
73
|
+
`);
|
|
74
|
+
});
|
|
75
|
+
test('reads an omitted API key from stdin', () => {
|
|
76
|
+
expect(resolveLoginAction({
|
|
77
|
+
options: { provider: 'google' },
|
|
78
|
+
daemonProvider: undefined,
|
|
79
|
+
isAgent: true,
|
|
80
|
+
})).toMatchInlineSnapshot(`
|
|
81
|
+
{
|
|
82
|
+
"key": undefined,
|
|
83
|
+
"provider": "google",
|
|
84
|
+
"type": "api-key",
|
|
85
|
+
}
|
|
86
|
+
`);
|
|
87
|
+
});
|
|
88
|
+
test('uses the provider picker for an interactive user', () => {
|
|
89
|
+
expect(resolveLoginAction({
|
|
90
|
+
options: {},
|
|
91
|
+
daemonProvider: undefined,
|
|
92
|
+
isAgent: false,
|
|
93
|
+
})).toMatchInlineSnapshot(`
|
|
94
|
+
{
|
|
95
|
+
"type": "interactive",
|
|
96
|
+
}
|
|
97
|
+
`);
|
|
98
|
+
});
|
|
99
|
+
test('does not call a locally unexpired ChatGPT token valid', () => {
|
|
100
|
+
expect(describeChatGptExpiry(2_000, 1_000)).toMatchInlineSnapshot(`"not expired locally"`);
|
|
101
|
+
expect(describeChatGptExpiry(1_000, 2_000)).toMatchInlineSnapshot(`"expired locally, the next request will try to refresh"`);
|
|
102
|
+
});
|
|
103
|
+
test('tells the user to sign in again when ChatGPT revokes the token', () => {
|
|
104
|
+
expect(isRevokedChatGptAccessToken(401, '{"message":"Encountered invalidated oauth token for user, failing request","code":"token_revoked"}')).toBe(true);
|
|
105
|
+
expect(isRevokedChatGptAccessToken(401, 'missing api key')).toBe(false);
|
|
106
|
+
expect(chatGptReLoginMessage({
|
|
107
|
+
status: 401,
|
|
108
|
+
body: 'Token refresh failed',
|
|
109
|
+
})).toMatchInlineSnapshot(`"ChatGPT rejected the saved login (401: Token refresh failed). Run: egaki login --provider chatgpt"`);
|
|
110
|
+
});
|
|
111
|
+
test('a stale callback does not cancel the active ChatGPT login', () => {
|
|
112
|
+
expect(oauthCallbackAction({
|
|
113
|
+
pendingState: 'new-state',
|
|
114
|
+
callbackState: 'old-state',
|
|
115
|
+
error: 'access_denied',
|
|
116
|
+
code: null,
|
|
117
|
+
})).toBe('ignore');
|
|
118
|
+
expect(oauthCallbackAction({
|
|
119
|
+
pendingState: 'new-state',
|
|
120
|
+
callbackState: 'new-state',
|
|
121
|
+
error: 'access_denied',
|
|
122
|
+
code: null,
|
|
123
|
+
})).toBe('provider-error');
|
|
124
|
+
expect(oauthCallbackAction({
|
|
125
|
+
pendingState: 'new-state',
|
|
126
|
+
callbackState: 'new-state',
|
|
127
|
+
error: null,
|
|
128
|
+
code: 'auth-code',
|
|
129
|
+
})).toBe('exchange');
|
|
130
|
+
});
|
|
131
|
+
test('picks the highest-priority ChatGPT model for the saved plan', () => {
|
|
132
|
+
expect(pickChatGptResponsesModel({
|
|
133
|
+
plan: 'prolite',
|
|
134
|
+
models: [
|
|
135
|
+
{ slug: 'gpt-5.4', visibility: 'hide', priority: 0, plans: ['prolite'] },
|
|
136
|
+
{ slug: 'gpt-6-astra', visibility: 'list', priority: 1, plans: ['prolite'] },
|
|
137
|
+
{ slug: 'gpt-5.5', visibility: 'list', priority: 12, plans: ['pro'] },
|
|
138
|
+
],
|
|
139
|
+
})).toBe('gpt-6-astra');
|
|
140
|
+
expect(pickChatGptResponsesModel({
|
|
141
|
+
plan: 'prolite',
|
|
142
|
+
exclude: ['gpt-6-astra'],
|
|
143
|
+
models: [
|
|
144
|
+
{ slug: 'gpt-6-astra', visibility: 'list', priority: 1, plans: ['prolite'] },
|
|
145
|
+
{ slug: 'gpt-5.5', visibility: 'list', priority: 12, plans: ['prolite'] },
|
|
146
|
+
],
|
|
147
|
+
})).toBe('gpt-5.5');
|
|
148
|
+
});
|
|
149
|
+
test('does not open an interactive prompt for an agent', () => {
|
|
150
|
+
expect(resolveLoginAction({
|
|
151
|
+
options: {},
|
|
152
|
+
daemonProvider: undefined,
|
|
153
|
+
isAgent: true,
|
|
154
|
+
})).toMatchInlineSnapshot(`
|
|
155
|
+
[Error: Missing --provider. Run egaki login --provider <name>.]
|
|
156
|
+
`);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-browser.d.ts","sourceRoot":"","sources":["../../src/cli/open-browser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open-browser.d.ts","sourceRoot":"","sources":["../../src/cli/open-browser.ts"],"names":[],"mappings":"AAuBA,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,EAAE,mBAA2B,EAAE;;CAAK,GACnC,OAAO,CAsBT"}
|
package/dist/cli/open-browser.js
CHANGED
|
@@ -14,9 +14,9 @@ function getOpenCommands(url) {
|
|
|
14
14
|
{ command: 'gio', args: ['open', url] },
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
|
-
export function openUrlInBrowser(url) {
|
|
17
|
+
export function openUrlInBrowser(url, { allowNonInteractive = false } = {}) {
|
|
18
18
|
// Avoid launching browser commands in non-interactive contexts (CI/pipes).
|
|
19
|
-
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
19
|
+
if (!allowNonInteractive && (!process.stdout.isTTY || !process.stdin.isTTY)) {
|
|
20
20
|
return false;
|
|
21
21
|
}
|
|
22
22
|
for (const candidate of getOpenCommands(url)) {
|
package/dist/cli/xai-auth.d.ts
CHANGED
|
@@ -19,7 +19,9 @@ export declare function refreshXaiToken(refreshToken: string): Promise<TokenResp
|
|
|
19
19
|
* Run the xAI OAuth browser flow. Opens the system browser for authorization,
|
|
20
20
|
* waits for the callback, and returns an XaiAuth object with tokens.
|
|
21
21
|
*/
|
|
22
|
-
export declare function xaiOAuthLogin(
|
|
22
|
+
export declare function xaiOAuthLogin({ openInBackground, }?: {
|
|
23
|
+
openInBackground?: boolean | undefined;
|
|
24
|
+
}): Promise<XaiAuth>;
|
|
23
25
|
/**
|
|
24
26
|
* Returns a valid XaiAuth, refreshing the access token if it's expired or
|
|
25
27
|
* about to expire. Calls `save` to persist the refreshed tokens.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xai-auth.d.ts","sourceRoot":"","sources":["../../src/cli/xai-auth.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAwBvB,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,QAAA,MAAM,mBAAmB;;;;;iBAKvB,CAAA;AAEF,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AA+DxD,8EAA8E;AAC9E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,GAAE,MAAqC,GAC5C,OAAO,CAKT;AA6BD,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAelF;AAwHD;;;GAGG;AACH,wBAAsB,aAAa,
|
|
1
|
+
{"version":3,"file":"xai-auth.d.ts","sourceRoot":"","sources":["../../src/cli/xai-auth.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAwBvB,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,QAAA,MAAM,mBAAmB;;;;;iBAKvB,CAAA;AAEF,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AA+DxD,8EAA8E;AAC9E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,GAAE,MAAqC,GAC5C,OAAO,CAKT;AA6BD,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAelF;AAwHD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAClC,gBAAwB,GACzB;;CAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CA8ExB;AAID;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAC5B,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,CAuB1B"}
|
package/dist/cli/xai-auth.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// when creating the @ai-sdk/xai provider instance.
|
|
13
13
|
import { createServer } from 'node:http';
|
|
14
14
|
import { spinner, log, note } from '@clack/prompts';
|
|
15
|
-
import pc from '
|
|
15
|
+
import { colors as pc } from 'goke';
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { openUrlInBrowser } from './open-browser.js';
|
|
18
18
|
import pkg from '../../package.json' with { type: 'json' };
|
|
@@ -236,7 +236,7 @@ function waitForOAuthCallback(verifier, state) {
|
|
|
236
236
|
* Run the xAI OAuth browser flow. Opens the system browser for authorization,
|
|
237
237
|
* waits for the callback, and returns an XaiAuth object with tokens.
|
|
238
238
|
*/
|
|
239
|
-
export async function xaiOAuthLogin() {
|
|
239
|
+
export async function xaiOAuthLogin({ openInBackground = false, } = {}) {
|
|
240
240
|
const s = spinner();
|
|
241
241
|
s.start('Starting xAI OAuth server...');
|
|
242
242
|
let server;
|
|
@@ -273,7 +273,17 @@ export async function xaiOAuthLogin() {
|
|
|
273
273
|
// the loopback server almost instantly. Registering after openUrlInBrowser
|
|
274
274
|
// creates a race where the callback arrives before pendingOAuth exists.
|
|
275
275
|
const callbackPromise = waitForOAuthCallback(pkce.verifier, state);
|
|
276
|
-
|
|
276
|
+
const opened = openUrlInBrowser(authUrl, {
|
|
277
|
+
allowNonInteractive: openInBackground,
|
|
278
|
+
});
|
|
279
|
+
if (openInBackground && !opened) {
|
|
280
|
+
const error = new Error('Could not open a browser for xAI authorization');
|
|
281
|
+
pendingOAuth?.reject(error);
|
|
282
|
+
pendingOAuth = undefined;
|
|
283
|
+
server.close();
|
|
284
|
+
await callbackPromise;
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
277
287
|
s.start('Waiting for authorization...');
|
|
278
288
|
try {
|
|
279
289
|
const tokens = await callbackPromise;
|
|
@@ -30,6 +30,11 @@ export interface AngledScreenProps {
|
|
|
30
30
|
fog?: number;
|
|
31
31
|
/** Film grain intensity. Default 0.02. */
|
|
32
32
|
grainIntensity?: number;
|
|
33
|
+
/** Chromatic aberration strength. RGB channels separate along the radial
|
|
34
|
+
* axis from frame center, scaled up by local blur so fringing reads
|
|
35
|
+
* strongest in the bokeh (like real lens CA on the reference photos).
|
|
36
|
+
* 0 = off. Default 0.45. */
|
|
37
|
+
chromaticAberration?: number;
|
|
33
38
|
/** Background color behind the tilted screen. Default '#000000'. */
|
|
34
39
|
backgroundColor?: string;
|
|
35
40
|
/** Width of the inner content wrapper. Default '80%'. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"angled-screen-shader.d.ts","sourceRoot":"","sources":["../../src/vite/angled-screen-shader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"angled-screen-shader.d.ts","sourceRoot":"","sources":["../../src/vite/angled-screen-shader.tsx"],"names":[],"mappings":"AA4EA,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAKhG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAA;IAGnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IAGnB,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;2EAEuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;mFAC+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;uEAEmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;IAGd,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0CAA0C;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;iCAG6B;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAE5B,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAA;IAGxB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAExB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAA;IAEf,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAwPD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BA8WpD"}
|
|
@@ -11,7 +11,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
* intersection → plane-local UV → sample DOM texture
|
|
12
12
|
*
|
|
13
13
|
* The intersection distance gives true per-pixel depth, which drives:
|
|
14
|
-
* - bokeh blur (
|
|
14
|
+
* - bokeh blur (sparse 6-blade hex iris, radius grows with distance from
|
|
15
|
+
* focus — fewer taps so the aperture shape reads as hard disks instead
|
|
16
|
+
* of smoothing into a gaussian)
|
|
17
|
+
* - chromatic aberration (radial R/B split, stronger in the bokeh)
|
|
15
18
|
* - fog toward backgroundColor (like the original Three.js plugin's
|
|
16
19
|
* scene fog, unframer-private/plugin-angled-screen BokehPass setup)
|
|
17
20
|
* - film grain (from the plugin's filmGrainShader, u_time-seeded)
|
|
@@ -94,6 +97,7 @@ uniform float u_focusDist; // focus distance in px along the ray
|
|
|
94
97
|
uniform float u_aspect; // width / height, for circular bokeh
|
|
95
98
|
uniform float u_fog;
|
|
96
99
|
uniform float u_grain;
|
|
100
|
+
uniform float u_ca; // chromatic aberration strength
|
|
97
101
|
uniform vec4 u_background;
|
|
98
102
|
uniform float u_debug;
|
|
99
103
|
uniform float u_time;
|
|
@@ -105,21 +109,26 @@ float rand(vec2 co) {
|
|
|
105
109
|
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
vec2(
|
|
112
|
+
// Sparse 6-blade hexagonal iris. Far fewer taps than the 41-sample Martins
|
|
113
|
+
// Upitis kernel so the aperture SHAPE reads as discrete hex disks instead of
|
|
114
|
+
// smoothing into a gaussian. Outer ring is weighted heavier so bright points
|
|
115
|
+
// form hard-edged bokeh blobs (the look of real lens DOF on the refs).
|
|
116
|
+
// Unit circle coords at radius 1.0 / 0.55.
|
|
117
|
+
const vec2 HEX_OUTER[6] = vec2[](
|
|
118
|
+
vec2( 1.0000, 0.0000),
|
|
119
|
+
vec2( 0.5000, 0.8660),
|
|
120
|
+
vec2(-0.5000, 0.8660),
|
|
121
|
+
vec2(-1.0000, 0.0000),
|
|
122
|
+
vec2(-0.5000, -0.8660),
|
|
123
|
+
vec2( 0.5000, -0.8660)
|
|
115
124
|
);
|
|
116
|
-
const vec2
|
|
117
|
-
vec2(
|
|
118
|
-
vec2(
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
vec2(
|
|
122
|
-
vec2(
|
|
125
|
+
const vec2 HEX_MID[6] = vec2[](
|
|
126
|
+
vec2( 0.5500, 0.0000),
|
|
127
|
+
vec2( 0.2750, 0.4763),
|
|
128
|
+
vec2(-0.2750, 0.4763),
|
|
129
|
+
vec2(-0.5500, 0.0000),
|
|
130
|
+
vec2(-0.2750, -0.4763),
|
|
131
|
+
vec2( 0.2750, -0.4763)
|
|
123
132
|
);
|
|
124
133
|
|
|
125
134
|
// Intersect the camera ray through screen uv suv with the tilted plane.
|
|
@@ -154,6 +163,17 @@ vec4 sampleScene(vec2 suv) {
|
|
|
154
163
|
return mix(u_background, c, coverage);
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
// Per-channel sample with chromatic aberration: R and B pull apart along
|
|
167
|
+
// the radial axis from frame center (classic lens CA purple/cyan fringing).
|
|
168
|
+
vec4 sampleSceneCA(vec2 suv, vec2 caOffset) {
|
|
169
|
+
float r = sampleScene(suv + caOffset).r;
|
|
170
|
+
float g = sampleScene(suv).g;
|
|
171
|
+
float b = sampleScene(suv - caOffset).b;
|
|
172
|
+
// Coverage/alpha from the green (unshifted) sample keeps silhouette stable.
|
|
173
|
+
vec4 mid = sampleScene(suv);
|
|
174
|
+
return vec4(r, g, b, mid.a);
|
|
175
|
+
}
|
|
176
|
+
|
|
157
177
|
void main() {
|
|
158
178
|
vec4 bg = u_background;
|
|
159
179
|
|
|
@@ -178,27 +198,41 @@ void main() {
|
|
|
178
198
|
// stays tack sharp. Background pixels (no hit) count as far away, so the
|
|
179
199
|
// plane silhouette melts softly where it is out of focus.
|
|
180
200
|
float factor = hit ? max(t - u_focusDist, 0.0) / u_perspective : 1.0;
|
|
181
|
-
|
|
201
|
+
float blurAmt = clamp(factor * u_aperture, 0.0, u_maxblur) * u_bokehEnabled;
|
|
202
|
+
vec2 dofblur = vec2(blurAmt);
|
|
182
203
|
|
|
183
204
|
vec2 aspectcorrect = vec2(1.0, u_aspect);
|
|
184
|
-
vec2 dofblur9 = dofblur * 0.9;
|
|
185
|
-
vec2 dofblur7 = dofblur * 0.7;
|
|
186
|
-
vec2 dofblur4 = dofblur * 0.4;
|
|
187
205
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
// Radial CA direction from frame center. Strength grows with local blur so
|
|
207
|
+
// sharp regions stay clean and only the bokeh fringes with purple/cyan.
|
|
208
|
+
vec2 fromCenter = v_uv - 0.5;
|
|
209
|
+
float radial = length(fromCenter);
|
|
210
|
+
vec2 caDir = radial > 1e-5 ? fromCenter / radial : vec2(1.0, 0.0);
|
|
211
|
+
// Base fringe + blur-scaled fringe. u_ca ~0.45 reads like a real lens on
|
|
212
|
+
// the reference photos without turning the whole frame into a prism.
|
|
213
|
+
float caAmt = u_ca * (0.0015 + blurAmt * 0.55);
|
|
214
|
+
vec2 caOffset = caDir * caAmt * aspectcorrect;
|
|
215
|
+
|
|
216
|
+
// Sparse hex gather. Outer ring weight 2.0 so the 6-blade iris is visible
|
|
217
|
+
// as hard bokeh disks; mid ring 1.0 fills the disk; center 1.0 anchors.
|
|
218
|
+
// Total weight = 6*2 + 6*1 + 1 = 19.
|
|
219
|
+
const float W_OUTER = 2.0;
|
|
220
|
+
const float W_MID = 1.0;
|
|
221
|
+
const float W_CENTER = 1.0;
|
|
222
|
+
vec4 col = sampleSceneCA(v_uv, caOffset) * W_CENTER;
|
|
223
|
+
for (int i = 0; i < 6; i++) {
|
|
224
|
+
col += sampleSceneCA(v_uv + HEX_OUTER[i] * aspectcorrect * dofblur, caOffset) * W_OUTER;
|
|
197
225
|
}
|
|
198
|
-
for (int i = 0; i <
|
|
199
|
-
col +=
|
|
226
|
+
for (int i = 0; i < 6; i++) {
|
|
227
|
+
col += sampleSceneCA(v_uv + HEX_MID[i] * aspectcorrect * dofblur, caOffset) * W_MID;
|
|
200
228
|
}
|
|
201
|
-
col /=
|
|
229
|
+
col /= (6.0 * W_OUTER + 6.0 * W_MID + W_CENTER);
|
|
230
|
+
|
|
231
|
+
// Mild highlight bias: bright samples punch through more (classic optical
|
|
232
|
+
// bokeh where speculars form bright disks instead of averaging down).
|
|
233
|
+
// Applied as a soft lift so it doesn't blow whites on already-bright UI.
|
|
234
|
+
float luma = dot(col.rgb, vec3(0.299, 0.587, 0.114));
|
|
235
|
+
col.rgb += col.rgb * blurAmt * 1.8 * smoothstep(0.55, 0.95, luma);
|
|
202
236
|
|
|
203
237
|
// Fog toward background with depth (plugin's THREE.Fog equivalent)
|
|
204
238
|
col.rgb = mix(col.rgb, bg.rgb, u_fog * depth01 * (hit ? 1.0 : 0.0));
|
|
@@ -226,6 +260,7 @@ const UNIFORM_NAMES = [
|
|
|
226
260
|
'u_aspect',
|
|
227
261
|
'u_fog',
|
|
228
262
|
'u_grain',
|
|
263
|
+
'u_ca',
|
|
229
264
|
'u_background',
|
|
230
265
|
'u_debug',
|
|
231
266
|
'u_time',
|
|
@@ -309,12 +344,18 @@ export function AngledScreen(props) {
|
|
|
309
344
|
step: 0.01,
|
|
310
345
|
},
|
|
311
346
|
fog: { value: props.fog ?? 0.35, min: 0, max: 1, step: 0.01 },
|
|
312
|
-
grainIntensity: { value: props.grainIntensity ?? 0.02, min: 0, max: 0.
|
|
347
|
+
grainIntensity: { value: props.grainIntensity ?? 0.02, min: 0, max: 0.3, step: 0.005 },
|
|
348
|
+
chromaticAberration: {
|
|
349
|
+
value: props.chromaticAberration ?? 0.45,
|
|
350
|
+
min: 0,
|
|
351
|
+
max: 2,
|
|
352
|
+
step: 0.01,
|
|
353
|
+
},
|
|
313
354
|
backgroundColor: props.backgroundColor ?? '#000000',
|
|
314
355
|
debug: props.debug ?? false,
|
|
315
356
|
});
|
|
316
357
|
const { perspective, rotateX, rotateY, rotateZ, translateX, translateZ } = tp;
|
|
317
|
-
const { aperture, maxBlur, focus, fog, grainIntensity } = tp;
|
|
358
|
+
const { aperture, maxBlur, focus, fog, grainIntensity, chromaticAberration } = tp;
|
|
318
359
|
const { bokeh, debug, backgroundColor } = tp;
|
|
319
360
|
const time = frame / fps;
|
|
320
361
|
const onInit = useCallback(({ canvas }) => {
|
|
@@ -473,6 +514,8 @@ export function AngledScreen(props) {
|
|
|
473
514
|
gl.uniform1f(locs.u_fog, fog);
|
|
474
515
|
if (locs.u_grain)
|
|
475
516
|
gl.uniform1f(locs.u_grain, grainIntensity);
|
|
517
|
+
if (locs.u_ca)
|
|
518
|
+
gl.uniform1f(locs.u_ca, chromaticAberration);
|
|
476
519
|
if (locs.u_background)
|
|
477
520
|
gl.uniform4fv(locs.u_background, parseHexColor(backgroundColor));
|
|
478
521
|
if (locs.u_debug)
|
|
@@ -507,7 +550,7 @@ export function AngledScreen(props) {
|
|
|
507
550
|
}
|
|
508
551
|
}, [
|
|
509
552
|
compW, compH, perspective, rotateX, rotateY, rotateZ, translateX, translateZ,
|
|
510
|
-
bokeh, aperture, maxBlur, focus, fog, grainIntensity,
|
|
553
|
+
bokeh, aperture, maxBlur, focus, fog, grainIntensity, chromaticAberration,
|
|
511
554
|
backgroundColor, debug, time,
|
|
512
555
|
]);
|
|
513
556
|
const supported = typeof document !== 'undefined' && HtmlInCanvas.isSupported();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-media-client.d.ts","sourceRoot":"","sources":["../../src/vite/generated-media-client.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAkC,KAAK,cAAc,EAAkB,MAAM,OAAO,CAAA;AAE3F,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAkB,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE7E,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"generated-media-client.d.ts","sourceRoot":"","sources":["../../src/vite/generated-media-client.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAkC,KAAK,cAAc,EAAkB,MAAM,OAAO,CAAA;AAE3F,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAkB,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE7E,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,CAAA;AAwDrD,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,EACV,WAAW,EACX,GAAG,IAAI,EACR,EAAE;IACD,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,cAAc,CAAC,OAAO,GAAG,CAAC,+BAM7B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,EACV,WAAW,EACX,GAAG,IAAI,EACR,EAAE;IACD,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,cAAc,CAAC,OAAO,KAAK,CAAC,+BAM/B;AAED,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,WAAW,EACX,GAAG,IAAI,EACR,EAAE;IACD,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,cAAc,CAAC,OAAO,KAAK,CAAC,+BAM/B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* Client wrappers for server-generated media components.
|
|
5
5
|
*
|
|
@@ -60,7 +60,7 @@ function ResolvedVideo({ srcPromise, ...rest }) {
|
|
|
60
60
|
}
|
|
61
61
|
function ResolvedAudio({ srcPromise, ...rest }) {
|
|
62
62
|
const src = use(srcPromise);
|
|
63
|
-
return _jsx(Audio, { src: src, ...rest });
|
|
63
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { "data-egaki-tts": true, style: { display: 'none' }, "aria-hidden": true }), _jsx(Audio, { src: src, ...rest })] }));
|
|
64
64
|
}
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
66
|
// Exported client wrappers — render fallback while promise is pending
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AAgDhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiDD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CA+elE"}
|