@sync.so/mcp-server 0.1.3 → 0.2.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/README.md +261 -86
- package/deploy/generation-status/app.html.gz +0 -0
- package/deploy/generation-status/release.json +6 -0
- package/dist/abort-signals.d.ts +6 -0
- package/dist/abort-signals.d.ts.map +1 -0
- package/dist/abort-signals.js +23 -0
- package/dist/account-features.d.ts +7 -0
- package/dist/account-features.d.ts.map +1 -0
- package/dist/account-features.js +48 -0
- package/dist/app-upload-relay.d.ts +13 -0
- package/dist/app-upload-relay.d.ts.map +1 -0
- package/dist/app-upload-relay.js +136 -0
- package/dist/asset-thumbnail.d.ts +3 -0
- package/dist/asset-thumbnail.d.ts.map +1 -0
- package/dist/asset-thumbnail.js +2 -0
- package/dist/auth/async-context.d.ts +5 -0
- package/dist/auth/async-context.d.ts.map +1 -0
- package/dist/auth/async-context.js +14 -0
- package/dist/auth/bearer-auth.d.ts +16 -0
- package/dist/auth/bearer-auth.d.ts.map +1 -0
- package/dist/auth/bearer-auth.js +55 -0
- package/dist/auth/client-credentials.d.ts +13 -0
- package/dist/auth/client-credentials.d.ts.map +1 -0
- package/dist/auth/client-credentials.js +67 -0
- package/dist/auth/device-auth.d.ts.map +1 -1
- package/dist/auth/device-auth.js +3 -2
- package/dist/auth/fixture-cleanup.d.ts +4 -0
- package/dist/auth/fixture-cleanup.d.ts.map +1 -0
- package/dist/auth/fixture-cleanup.js +9 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -0
- package/dist/auth/oauth-diagnostics.d.ts +11 -0
- package/dist/auth/oauth-diagnostics.d.ts.map +1 -0
- package/dist/auth/oauth-diagnostics.js +78 -0
- package/dist/auth/oauth-provider.d.ts +16 -0
- package/dist/auth/oauth-provider.d.ts.map +1 -0
- package/dist/auth/oauth-provider.js +107 -0
- package/dist/auth/resource-metadata.d.ts +2 -0
- package/dist/auth/resource-metadata.d.ts.map +1 -0
- package/dist/auth/resource-metadata.js +3 -0
- package/dist/auth/token-verification.d.ts +11 -0
- package/dist/auth/token-verification.d.ts.map +1 -0
- package/dist/auth/token-verification.js +99 -0
- package/dist/auth/verification-cache.d.ts +8 -0
- package/dist/auth/verification-cache.d.ts.map +1 -0
- package/dist/auth/verification-cache.js +111 -0
- package/dist/chatgpt-analytics.d.ts +22 -0
- package/dist/chatgpt-analytics.d.ts.map +1 -0
- package/dist/chatgpt-analytics.js +172 -0
- package/dist/chatgpt-app.d.ts +15 -0
- package/dist/chatgpt-app.d.ts.map +1 -0
- package/dist/chatgpt-app.js +133 -0
- package/dist/chatgpt-release.d.ts +12 -0
- package/dist/chatgpt-release.d.ts.map +1 -0
- package/dist/chatgpt-release.js +30 -0
- package/dist/cli.js +30 -16
- package/dist/client-profile.d.ts +10 -0
- package/dist/client-profile.d.ts.map +1 -0
- package/dist/client-profile.js +45 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -0
- package/dist/generation-status-html.d.ts +2 -0
- package/dist/generation-status-html.d.ts.map +1 -0
- package/dist/generation-status-html.js +26 -0
- package/dist/generation-status.d.ts +6 -0
- package/dist/generation-status.d.ts.map +1 -0
- package/dist/generation-status.js +37 -0
- package/dist/http-client.d.ts +45 -1
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +171 -34
- package/dist/http-server.d.ts +53 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +753 -0
- package/dist/http-session.d.ts +18 -0
- package/dist/http-session.d.ts.map +1 -0
- package/dist/http-session.js +189 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/oauth-response.d.ts +3 -0
- package/dist/oauth-response.d.ts.map +1 -0
- package/dist/oauth-response.js +30 -0
- package/dist/openapi/index.d.ts +2 -2
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/parser.d.ts +2 -0
- package/dist/openapi/parser.d.ts.map +1 -1
- package/dist/openapi/parser.js +41 -1
- package/dist/openapi/types.d.ts +3 -0
- package/dist/openapi/types.d.ts.map +1 -1
- package/dist/organization-selection.d.ts +61 -0
- package/dist/organization-selection.d.ts.map +1 -0
- package/dist/organization-selection.js +142 -0
- package/dist/rate-limits.d.ts +13 -0
- package/dist/rate-limits.d.ts.map +1 -0
- package/dist/rate-limits.js +67 -0
- package/dist/revision.d.ts +6 -0
- package/dist/revision.d.ts.map +1 -0
- package/dist/revision.js +17 -0
- package/dist/runtime-diagnostics.d.ts +32 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +58 -0
- package/dist/server.d.ts +23 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +893 -32
- package/dist/session-registry.d.ts +54 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +202 -0
- package/dist/session-telemetry.d.ts +13 -0
- package/dist/session-telemetry.d.ts.map +1 -0
- package/dist/session-telemetry.js +87 -0
- package/dist/spend-confirmation.d.ts +21 -0
- package/dist/spend-confirmation.d.ts.map +1 -0
- package/dist/spend-confirmation.js +105 -0
- package/dist/tool-analytics-context.d.ts +11 -0
- package/dist/tool-analytics-context.d.ts.map +1 -0
- package/dist/tool-analytics-context.js +8 -0
- package/dist/tool-names.d.ts +35 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tool-names.js +133 -0
- package/dist/tools/account-context.d.ts +5 -0
- package/dist/tools/account-context.d.ts.map +1 -0
- package/dist/tools/account-context.js +131 -0
- package/dist/tools/account-management.d.ts +4 -0
- package/dist/tools/account-management.d.ts.map +1 -0
- package/dist/tools/account-management.js +111 -0
- package/dist/tools/app-tools.d.ts +31 -0
- package/dist/tools/app-tools.d.ts.map +1 -0
- package/dist/tools/app-tools.js +350 -0
- package/dist/tools/asset-analysis.d.ts +4 -0
- package/dist/tools/asset-analysis.d.ts.map +1 -0
- package/dist/tools/asset-analysis.js +172 -0
- package/dist/tools/asset-creators.d.ts +4 -0
- package/dist/tools/asset-creators.d.ts.map +1 -0
- package/dist/tools/asset-creators.js +38 -0
- package/dist/tools/audio-trim.d.ts +6 -0
- package/dist/tools/audio-trim.d.ts.map +1 -0
- package/dist/tools/audio-trim.js +79 -0
- package/dist/tools/batch-results.d.ts +16 -0
- package/dist/tools/batch-results.d.ts.map +1 -0
- package/dist/tools/batch-results.js +103 -0
- package/dist/tools/batches.d.ts +28 -0
- package/dist/tools/batches.d.ts.map +1 -0
- package/dist/tools/batches.js +293 -0
- package/dist/tools/dialogue.d.ts +6 -0
- package/dist/tools/dialogue.d.ts.map +1 -0
- package/dist/tools/dialogue.js +309 -0
- package/dist/tools/estimate-duration.d.ts +35 -0
- package/dist/tools/estimate-duration.d.ts.map +1 -0
- package/dist/tools/estimate-duration.js +66 -0
- package/dist/tools/feedback.d.ts +14 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +72 -0
- package/dist/tools/generated-adapters.d.ts +25 -0
- package/dist/tools/generated-adapters.d.ts.map +1 -0
- package/dist/tools/generated-adapters.js +143 -0
- package/dist/tools/generation-context.d.ts +14 -0
- package/dist/tools/generation-context.d.ts.map +1 -0
- package/dist/tools/generation-context.js +39 -0
- package/dist/tools/generation-continuation.d.ts +4 -0
- package/dist/tools/generation-continuation.d.ts.map +1 -0
- package/dist/tools/generation-continuation.js +124 -0
- package/dist/tools/generation-costs.d.ts +6 -0
- package/dist/tools/generation-costs.d.ts.map +1 -0
- package/dist/tools/generation-costs.js +67 -0
- package/dist/tools/generation-output.d.ts +3 -0
- package/dist/tools/generation-output.d.ts.map +1 -0
- package/dist/tools/generation-output.js +39 -0
- package/dist/tools/generator.d.ts +35 -2
- package/dist/tools/generator.d.ts.map +1 -1
- package/dist/tools/generator.js +127 -9
- package/dist/tools/job-status.d.ts +35 -0
- package/dist/tools/job-status.d.ts.map +1 -0
- package/dist/tools/job-status.js +92 -0
- package/dist/tools/job-wait.d.ts +41 -0
- package/dist/tools/job-wait.d.ts.map +1 -0
- package/dist/tools/job-wait.js +84 -0
- package/dist/tools/jobs.d.ts +3 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +210 -0
- package/dist/tools/media-upload.d.ts +27 -0
- package/dist/tools/media-upload.d.ts.map +1 -0
- package/dist/tools/media-upload.js +247 -0
- package/dist/tools/model-options.d.ts +36 -0
- package/dist/tools/model-options.d.ts.map +1 -0
- package/dist/tools/model-options.js +48 -0
- package/dist/tools/organization-choice.d.ts +9 -0
- package/dist/tools/organization-choice.d.ts.map +1 -0
- package/dist/tools/organization-choice.js +136 -0
- package/dist/tools/organizations.d.ts +56 -0
- package/dist/tools/organizations.d.ts.map +1 -0
- package/dist/tools/organizations.js +235 -0
- package/dist/tools/output-schemas.d.ts +54 -0
- package/dist/tools/output-schemas.d.ts.map +1 -0
- package/dist/tools/output-schemas.js +58 -0
- package/dist/tools/overrides.d.ts +3 -0
- package/dist/tools/overrides.d.ts.map +1 -1
- package/dist/tools/overrides.js +113 -15
- package/dist/tools/playback-metadata.d.ts +4 -0
- package/dist/tools/playback-metadata.d.ts.map +1 -0
- package/dist/tools/playback-metadata.js +51 -0
- package/dist/tools/project-destination.d.ts +13 -0
- package/dist/tools/project-destination.d.ts.map +1 -0
- package/dist/tools/project-destination.js +45 -0
- package/dist/tools/project-history.d.ts +6 -0
- package/dist/tools/project-history.d.ts.map +1 -0
- package/dist/tools/project-history.js +45 -0
- package/dist/tools/project-intent.d.ts +8 -0
- package/dist/tools/project-intent.d.ts.map +1 -0
- package/dist/tools/project-intent.js +87 -0
- package/dist/tools/running-submissions.d.ts +7 -0
- package/dist/tools/running-submissions.d.ts.map +1 -0
- package/dist/tools/running-submissions.js +91 -0
- package/dist/tools/speech.d.ts +6 -0
- package/dist/tools/speech.d.ts.map +1 -0
- package/dist/tools/speech.js +116 -0
- package/dist/tools/studio-draft.d.ts +7 -0
- package/dist/tools/studio-draft.d.ts.map +1 -0
- package/dist/tools/studio-draft.js +195 -0
- package/dist/tools/studio-view-contract.d.ts +184 -0
- package/dist/tools/studio-view-contract.d.ts.map +1 -0
- package/dist/tools/studio-view-contract.js +67 -0
- package/dist/tools/studio-view.d.ts +6 -0
- package/dist/tools/studio-view.d.ts.map +1 -0
- package/dist/tools/studio-view.js +126 -0
- package/dist/tools/timeline-lipsync.d.ts +316 -0
- package/dist/tools/timeline-lipsync.d.ts.map +1 -0
- package/dist/tools/timeline-lipsync.js +304 -0
- package/dist/tools/translation-preview.d.ts +8 -0
- package/dist/tools/translation-preview.d.ts.map +1 -0
- package/dist/tools/translation-preview.js +146 -0
- package/dist/tools/translation.d.ts +6 -0
- package/dist/tools/translation.d.ts.map +1 -0
- package/dist/tools/translation.js +131 -0
- package/dist/tools/upload-widget.d.ts +13 -0
- package/dist/tools/upload-widget.d.ts.map +1 -0
- package/dist/tools/upload-widget.js +788 -0
- package/dist/toolsets.d.ts +18 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +157 -0
- package/dist/update-check.d.ts +38 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +150 -0
- package/dist/upload-runtime.d.ts +45 -0
- package/dist/upload-runtime.d.ts.map +1 -0
- package/dist/upload-runtime.js +156 -0
- package/dist/upload-source.d.ts +4 -0
- package/dist/upload-source.d.ts.map +1 -0
- package/dist/upload-source.js +46 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +79 -33
- package/dist/openapi/parser.test.d.ts +0 -2
- package/dist/openapi/parser.test.d.ts.map +0 -1
- package/dist/openapi/parser.test.js +0 -188
- package/dist/tools/generator.test.d.ts +0 -2
- package/dist/tools/generator.test.d.ts.map +0 -1
- package/dist/tools/generator.test.js +0 -178
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const MCP_APP_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
|
|
3
|
+
// v8 calls the renamed tools; v7 and earlier are still served for widgets ChatGPT kept.
|
|
4
|
+
export const UPLOAD_WIDGET_URI = 'ui://sync/upload-widget-v8.html';
|
|
5
|
+
export const UPLOAD_WIDGET_LEGACY_URIS = [
|
|
6
|
+
'ui://sync/upload-widget-v1.html',
|
|
7
|
+
'ui://sync/upload-widget-v2.html',
|
|
8
|
+
'ui://sync/upload-widget-v3.html',
|
|
9
|
+
'ui://sync/upload-widget-v4.html',
|
|
10
|
+
'ui://sync/upload-widget-v5.html',
|
|
11
|
+
'ui://sync/upload-widget-v6.html',
|
|
12
|
+
'ui://sync/upload-widget-v7.html',
|
|
13
|
+
];
|
|
14
|
+
const UPLOAD_WIDGET_RESOURCE_URIS = [UPLOAD_WIDGET_URI, ...UPLOAD_WIDGET_LEGACY_URIS];
|
|
15
|
+
const UPLOAD_WIDGET_DESCRIPTION = 'Select or upload media inside ChatGPT, stage it as a durable Sync asset, and report the assetId back into the conversation.';
|
|
16
|
+
const UPLOAD_WIDGET_RESOURCE_META = {
|
|
17
|
+
ui: {
|
|
18
|
+
prefersBorder: true,
|
|
19
|
+
csp: {
|
|
20
|
+
connectDomains: [],
|
|
21
|
+
resourceDomains: [],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
'openai/widgetDescription': UPLOAD_WIDGET_DESCRIPTION,
|
|
25
|
+
'openai/widgetPrefersBorder': true,
|
|
26
|
+
'openai/widgetCSP': {
|
|
27
|
+
connect_domains: [],
|
|
28
|
+
resource_domains: [],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const UPLOAD_WIDGET_RESOURCE_METADATA = {
|
|
32
|
+
title: 'Sync uploader',
|
|
33
|
+
description: UPLOAD_WIDGET_DESCRIPTION,
|
|
34
|
+
mimeType: MCP_APP_RESOURCE_MIME_TYPE,
|
|
35
|
+
_meta: UPLOAD_WIDGET_RESOURCE_META,
|
|
36
|
+
};
|
|
37
|
+
const uploadWidgetOutputSchema = {
|
|
38
|
+
requestedMediaType: z.enum(['image', 'audio']).optional(),
|
|
39
|
+
script: z.string().optional(),
|
|
40
|
+
};
|
|
41
|
+
export const UPLOAD_WIDGET_HTML = `
|
|
42
|
+
<!doctype html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="utf-8" />
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
47
|
+
<style>
|
|
48
|
+
:root {
|
|
49
|
+
color-scheme: light dark;
|
|
50
|
+
--bg: #ffffff;
|
|
51
|
+
--fg: #141414;
|
|
52
|
+
--muted: #666666;
|
|
53
|
+
--line: #d9d9d9;
|
|
54
|
+
--button-bg: #141414;
|
|
55
|
+
--button-fg: #ffffff;
|
|
56
|
+
--secondary-bg: #f4f4f4;
|
|
57
|
+
--secondary-fg: #171717;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (prefers-color-scheme: dark) {
|
|
61
|
+
:root {
|
|
62
|
+
--bg: #171717;
|
|
63
|
+
--fg: #f4f4f4;
|
|
64
|
+
--muted: #adadad;
|
|
65
|
+
--line: #3a3a3a;
|
|
66
|
+
--button-bg: #f4f4f4;
|
|
67
|
+
--button-fg: #171717;
|
|
68
|
+
--secondary-bg: #262626;
|
|
69
|
+
--secondary-fg: #f4f4f4;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
* {
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
body {
|
|
78
|
+
margin: 0;
|
|
79
|
+
background: var(--bg);
|
|
80
|
+
color: var(--fg);
|
|
81
|
+
font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main {
|
|
85
|
+
display: grid;
|
|
86
|
+
gap: 12px;
|
|
87
|
+
padding: 16px;
|
|
88
|
+
max-width: 520px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
h1 {
|
|
92
|
+
margin: 0;
|
|
93
|
+
font-size: 16px;
|
|
94
|
+
font-weight: 650;
|
|
95
|
+
letter-spacing: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
p {
|
|
99
|
+
margin: 0;
|
|
100
|
+
color: var(--muted);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.actions {
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-wrap: wrap;
|
|
106
|
+
gap: 8px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.script-field {
|
|
110
|
+
display: grid;
|
|
111
|
+
gap: 6px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.script-field span {
|
|
115
|
+
color: var(--muted);
|
|
116
|
+
font-size: 12px;
|
|
117
|
+
font-weight: 650;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
input[type="text"] {
|
|
121
|
+
min-height: 36px;
|
|
122
|
+
border: 1px solid var(--line);
|
|
123
|
+
border-radius: 6px;
|
|
124
|
+
padding: 0 10px;
|
|
125
|
+
background: transparent;
|
|
126
|
+
color: var(--fg);
|
|
127
|
+
font: inherit;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
button {
|
|
131
|
+
min-height: 36px;
|
|
132
|
+
border: 1px solid var(--line);
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
padding: 0 12px;
|
|
135
|
+
font: inherit;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
button.primary {
|
|
140
|
+
background: var(--button-bg);
|
|
141
|
+
border-color: var(--button-bg);
|
|
142
|
+
color: var(--button-fg);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
button.secondary {
|
|
146
|
+
background: var(--secondary-bg);
|
|
147
|
+
color: var(--secondary-fg);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
button:disabled {
|
|
151
|
+
cursor: not-allowed;
|
|
152
|
+
opacity: 0.55;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.status {
|
|
156
|
+
min-height: 44px;
|
|
157
|
+
border: 1px solid var(--line);
|
|
158
|
+
border-radius: 6px;
|
|
159
|
+
padding: 10px 12px;
|
|
160
|
+
overflow-wrap: anywhere;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.status strong {
|
|
164
|
+
display: block;
|
|
165
|
+
margin-bottom: 2px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
code {
|
|
169
|
+
font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
input[type="file"] {
|
|
173
|
+
display: none;
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
176
|
+
</head>
|
|
177
|
+
<body>
|
|
178
|
+
<main>
|
|
179
|
+
<h1>Upload to Sync</h1>
|
|
180
|
+
<p id="hint">Choose a ChatGPT file or upload a local image or audio file. Attach videos in chat first.</p>
|
|
181
|
+
<label class="script-field" for="scriptInput">
|
|
182
|
+
<span>Script</span>
|
|
183
|
+
<input id="scriptInput" type="text" autocomplete="off" placeholder="Text for the image to say" />
|
|
184
|
+
</label>
|
|
185
|
+
<div class="actions">
|
|
186
|
+
<button id="selectFile" class="primary" type="button">Choose from ChatGPT</button>
|
|
187
|
+
<button id="uploadLocal" class="secondary" type="button">Upload local file</button>
|
|
188
|
+
<button id="runLipsync" class="secondary" type="button">Run lipsync</button>
|
|
189
|
+
<input id="localFile" type="file" accept="image/*,audio/*" />
|
|
190
|
+
</div>
|
|
191
|
+
<div class="status" id="status" aria-live="polite">
|
|
192
|
+
<strong>Ready</strong>
|
|
193
|
+
<span>Waiting for media.</span>
|
|
194
|
+
</div>
|
|
195
|
+
</main>
|
|
196
|
+
|
|
197
|
+
<script>
|
|
198
|
+
(function () {
|
|
199
|
+
var openai = window.openai;
|
|
200
|
+
function bridgeObject(value) {
|
|
201
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function bridgeStructuredContent(sourceOpenai) {
|
|
205
|
+
var metadata = bridgeObject(sourceOpenai && sourceOpenai.toolResponseMetadata);
|
|
206
|
+
var callToolResult = bridgeObject(metadata.call_tool_result);
|
|
207
|
+
var mcpToolResult = bridgeObject(metadata.mcp_tool_result);
|
|
208
|
+
return Object.assign(
|
|
209
|
+
{},
|
|
210
|
+
bridgeObject(mcpToolResult._meta),
|
|
211
|
+
bridgeObject(callToolResult._meta),
|
|
212
|
+
bridgeObject(mcpToolResult.structuredContent),
|
|
213
|
+
bridgeObject(callToolResult.structuredContent)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function bridgeInputFrom(sourceOpenai) {
|
|
218
|
+
return Object.assign(
|
|
219
|
+
{},
|
|
220
|
+
bridgeStructuredContent(sourceOpenai),
|
|
221
|
+
bridgeObject(sourceOpenai && sourceOpenai.toolOutput),
|
|
222
|
+
bridgeObject(sourceOpenai && sourceOpenai.toolInput)
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
var input = bridgeInputFrom(openai);
|
|
227
|
+
var requestedMediaType = typeof input.requestedMediaType === "string" ? input.requestedMediaType : "";
|
|
228
|
+
var script = typeof input.script === "string" ? input.script : "";
|
|
229
|
+
var state = (openai && openai.widgetState && typeof openai.widgetState === "object")
|
|
230
|
+
? openai.widgetState
|
|
231
|
+
: {};
|
|
232
|
+
|
|
233
|
+
var selectButton = document.getElementById("selectFile");
|
|
234
|
+
var localButton = document.getElementById("uploadLocal");
|
|
235
|
+
var runButton = document.getElementById("runLipsync");
|
|
236
|
+
var localInput = document.getElementById("localFile");
|
|
237
|
+
var scriptInput = document.getElementById("scriptInput");
|
|
238
|
+
var status = document.getElementById("status");
|
|
239
|
+
var hint = document.getElementById("hint");
|
|
240
|
+
var isBusy = false;
|
|
241
|
+
|
|
242
|
+
function updateHint() {
|
|
243
|
+
if (script) {
|
|
244
|
+
hint.textContent = requestedMediaType
|
|
245
|
+
? "Choose the " + requestedMediaType + " for: " + script
|
|
246
|
+
: "Choose the media for: " + script;
|
|
247
|
+
} else {
|
|
248
|
+
hint.textContent = "Choose a ChatGPT file or upload a local image or audio file. Attach videos in chat first.";
|
|
249
|
+
}
|
|
250
|
+
if (openai && typeof openai.notifyIntrinsicHeight === "function") {
|
|
251
|
+
openai.notifyIntrinsicHeight();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function syncScriptInput() {
|
|
256
|
+
var nextScript = scriptInput.value.trim();
|
|
257
|
+
if (script === nextScript) {
|
|
258
|
+
updateRunButton();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
script = nextScript;
|
|
262
|
+
state = Object.assign({}, state, { script: script || undefined });
|
|
263
|
+
if (openai && typeof openai.setWidgetState === "function") {
|
|
264
|
+
openai.setWidgetState(state);
|
|
265
|
+
}
|
|
266
|
+
updateHint();
|
|
267
|
+
updateRunButton();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function escapeHtml(value) {
|
|
271
|
+
return String(value)
|
|
272
|
+
.replace(/&/g, "&")
|
|
273
|
+
.replace(/</g, "<")
|
|
274
|
+
.replace(/>/g, ">")
|
|
275
|
+
.replace(/"/g, """)
|
|
276
|
+
.replace(/'/g, "'");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function setStatus(title, detail, patch) {
|
|
280
|
+
state = Object.assign({}, state, patch || {}, {
|
|
281
|
+
statusTitle: title,
|
|
282
|
+
statusDetail: detail,
|
|
283
|
+
});
|
|
284
|
+
if (openai && typeof openai.setWidgetState === "function") {
|
|
285
|
+
openai.setWidgetState(state);
|
|
286
|
+
}
|
|
287
|
+
status.innerHTML = "<strong>" + escapeHtml(title) + "</strong><span>" + escapeHtml(detail) + "</span>";
|
|
288
|
+
if (openai && typeof openai.notifyIntrinsicHeight === "function") {
|
|
289
|
+
openai.notifyIntrinsicHeight();
|
|
290
|
+
}
|
|
291
|
+
updateRunButton();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function setBusy(nextBusy) {
|
|
295
|
+
isBusy = nextBusy;
|
|
296
|
+
selectButton.disabled = isBusy || !canSelectFiles();
|
|
297
|
+
localButton.disabled = isBusy || !canUploadLocal();
|
|
298
|
+
updateRunButton();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function canSelectFiles() {
|
|
302
|
+
return Boolean(openai && openai.selectFiles && openai.getFileDownloadUrl && openai.callTool);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function canUploadLocal() {
|
|
306
|
+
return Boolean(openai && openai.uploadFile && openai.getFileDownloadUrl && openai.callTool);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function canRunLipsync() {
|
|
310
|
+
return Boolean(
|
|
311
|
+
openai &&
|
|
312
|
+
openai.callTool &&
|
|
313
|
+
script &&
|
|
314
|
+
state.assetId &&
|
|
315
|
+
state.mediaType === "image" &&
|
|
316
|
+
!state.generationId &&
|
|
317
|
+
!state.lipsyncStarted
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function updateRunButton() {
|
|
322
|
+
runButton.disabled = isBusy || !canRunLipsync();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function applyBridgeInput(nextInput) {
|
|
326
|
+
var nextMediaType = typeof nextInput.requestedMediaType === "string" ? nextInput.requestedMediaType : "";
|
|
327
|
+
var nextScript = typeof nextInput.script === "string" ? nextInput.script : "";
|
|
328
|
+
var changed = false;
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
(nextMediaType === "image" || nextMediaType === "audio") &&
|
|
332
|
+
requestedMediaType !== nextMediaType
|
|
333
|
+
) {
|
|
334
|
+
requestedMediaType = nextMediaType;
|
|
335
|
+
changed = true;
|
|
336
|
+
}
|
|
337
|
+
if (nextScript && script !== nextScript) {
|
|
338
|
+
script = nextScript;
|
|
339
|
+
scriptInput.value = script;
|
|
340
|
+
changed = true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (changed) {
|
|
344
|
+
updateHint();
|
|
345
|
+
maybeRunPendingLipsync();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function syncBridgeInput(sourceOpenai) {
|
|
350
|
+
applyBridgeInput(bridgeInputFrom(sourceOpenai || openai));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function globalsFromEvent(event) {
|
|
354
|
+
return bridgeObject(event && event.detail && event.detail.globals);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function maybeRunPendingLipsync() {
|
|
358
|
+
if (!script || !state.assetId || state.mediaType !== "image") return;
|
|
359
|
+
if (state.generationId || state.lipsyncStarted || state.statusTitle !== "Uploaded") return;
|
|
360
|
+
state = Object.assign({}, state, { lipsyncStarted: true });
|
|
361
|
+
if (openai && typeof openai.setWidgetState === "function") {
|
|
362
|
+
openai.setWidgetState(state);
|
|
363
|
+
}
|
|
364
|
+
void runLipsyncFlow(state.assetId, state.mediaType).catch(function (error) {
|
|
365
|
+
setStatus("Lipsync failed", error && error.message ? error.message : String(error), {
|
|
366
|
+
lipsyncStarted: false,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function mediaTypeFrom(mimeType, fileName) {
|
|
372
|
+
if (requestedMediaType === "image" || requestedMediaType === "audio") {
|
|
373
|
+
return requestedMediaType;
|
|
374
|
+
}
|
|
375
|
+
var lowerMime = String(mimeType || "").toLowerCase();
|
|
376
|
+
if (lowerMime.indexOf("image/") === 0) return "image";
|
|
377
|
+
if (lowerMime.indexOf("audio/") === 0) return "audio";
|
|
378
|
+
var lowerName = String(fileName || "").toLowerCase();
|
|
379
|
+
if (/\\.(png|jpg|jpeg|webp|gif)$/.test(lowerName)) return "image";
|
|
380
|
+
if (/\\.(mp3|wav|m4a|aac|ogg)$/.test(lowerName)) return "audio";
|
|
381
|
+
return "";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function fileIdFrom(value) {
|
|
385
|
+
if (typeof value === "string") return value;
|
|
386
|
+
if (!value || typeof value !== "object") return "";
|
|
387
|
+
return value.fileId || value.file_id || value.id || "";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function downloadUrlFrom(value) {
|
|
391
|
+
if (typeof value === "string") return value;
|
|
392
|
+
if (!value || typeof value !== "object") return "";
|
|
393
|
+
return value.downloadUrl || value.download_url || value.url || value.href || "";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function parseJson(text) {
|
|
397
|
+
try {
|
|
398
|
+
return JSON.parse(text);
|
|
399
|
+
} catch (_error) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function payloadFromToolResult(value) {
|
|
405
|
+
if (!value || typeof value !== "object") return value;
|
|
406
|
+
if (value.structuredContent) return value.structuredContent;
|
|
407
|
+
if (value.call_tool_result) return payloadFromToolResult(value.call_tool_result);
|
|
408
|
+
if (value.mcp_tool_result) return payloadFromToolResult(value.mcp_tool_result);
|
|
409
|
+
if (Array.isArray(value.content)) {
|
|
410
|
+
for (var i = 0; i < value.content.length; i += 1) {
|
|
411
|
+
var item = value.content[i];
|
|
412
|
+
if (item && item.type === "text" && typeof item.text === "string") {
|
|
413
|
+
var parsed = parseJson(item.text);
|
|
414
|
+
if (parsed) return parsed;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function findStringByKeys(value, keys, depth) {
|
|
422
|
+
if (!value || typeof value !== "object" || depth > 8) return "";
|
|
423
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
424
|
+
var direct = value[keys[i]];
|
|
425
|
+
if (typeof direct === "string" && direct) return direct;
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(value)) {
|
|
428
|
+
for (var a = 0; a < value.length; a += 1) {
|
|
429
|
+
var fromArray = findStringByKeys(value[a], keys, depth + 1);
|
|
430
|
+
if (fromArray) return fromArray;
|
|
431
|
+
}
|
|
432
|
+
return "";
|
|
433
|
+
}
|
|
434
|
+
var objectKeys = Object.keys(value);
|
|
435
|
+
for (var k = 0; k < objectKeys.length; k += 1) {
|
|
436
|
+
var fromObject = findStringByKeys(value[objectKeys[k]], keys, depth + 1);
|
|
437
|
+
if (fromObject) return fromObject;
|
|
438
|
+
}
|
|
439
|
+
return "";
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function assetIdFrom(value) {
|
|
443
|
+
return findStringByKeys(payloadFromToolResult(value), ["assetId", "asset_id"], 0);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function generationIdFrom(value) {
|
|
447
|
+
return findStringByKeys(payloadFromToolResult(value), ["id", "generationId", "generation_id"], 0);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function generationStatusFrom(value) {
|
|
451
|
+
return findStringByKeys(payloadFromToolResult(value), ["status", "state"], 0);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function outputUrlFrom(value) {
|
|
455
|
+
// outputMediaUrl is the API's name; outputUrl and output_url are older names.
|
|
456
|
+
return findStringByKeys(payloadFromToolResult(value), ["outputMediaUrl", "outputUrl", "output_url"], 0);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function voiceFromToolResult(value) {
|
|
460
|
+
return findVoice(payloadFromToolResult(value), 0);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findVoice(value, depth) {
|
|
464
|
+
if (!value || typeof value !== "object" || depth > 8) return null;
|
|
465
|
+
if (Array.isArray(value)) {
|
|
466
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
467
|
+
var fromArray = findVoice(value[i], depth + 1);
|
|
468
|
+
if (fromArray) return fromArray;
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
if (typeof value.id === "string" && value.id) {
|
|
473
|
+
return {
|
|
474
|
+
id: value.id,
|
|
475
|
+
name: typeof value.name === "string" ? value.name : value.id,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
var preferred = ["voices", "data", "items", "results"];
|
|
479
|
+
for (var p = 0; p < preferred.length; p += 1) {
|
|
480
|
+
var fromPreferred = findVoice(value[preferred[p]], depth + 1);
|
|
481
|
+
if (fromPreferred) return fromPreferred;
|
|
482
|
+
}
|
|
483
|
+
var keys = Object.keys(value);
|
|
484
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
485
|
+
var fromObject = findVoice(value[keys[k]], depth + 1);
|
|
486
|
+
if (fromObject) return fromObject;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function delay(ms) {
|
|
492
|
+
return new Promise(function (resolve) {
|
|
493
|
+
setTimeout(resolve, ms);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function runLipsyncFlow(assetId, mediaType) {
|
|
498
|
+
if (!script || mediaType !== "image") return;
|
|
499
|
+
|
|
500
|
+
state = Object.assign({}, state, { lipsyncStarted: true });
|
|
501
|
+
if (openai && typeof openai.setWidgetState === "function") {
|
|
502
|
+
openai.setWidgetState(state);
|
|
503
|
+
}
|
|
504
|
+
setStatus("Choosing voice", "Finding an available Sync voice.", {
|
|
505
|
+
assetId: assetId,
|
|
506
|
+
mediaType: mediaType,
|
|
507
|
+
script: script,
|
|
508
|
+
lipsyncStarted: true,
|
|
509
|
+
});
|
|
510
|
+
var voicesResult = await openai.callTool("voices_list", {});
|
|
511
|
+
var voice = voiceFromToolResult(voicesResult);
|
|
512
|
+
if (!voice || !voice.id) {
|
|
513
|
+
throw new Error("No Sync voice id was returned.");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
setStatus("Creating lipsync", voice.name || voice.id, {
|
|
517
|
+
assetId: assetId,
|
|
518
|
+
mediaType: mediaType,
|
|
519
|
+
script: script,
|
|
520
|
+
voiceId: voice.id,
|
|
521
|
+
lipsyncStarted: true,
|
|
522
|
+
});
|
|
523
|
+
var createArgs = { imageAssetId: assetId, script: script, voiceId: voice.id };
|
|
524
|
+
var createResult = await openai.callTool("lipsync_create-from-assets", createArgs);
|
|
525
|
+
var generationId = generationIdFrom(createResult);
|
|
526
|
+
if (!generationId) {
|
|
527
|
+
throw new Error("Sync did not return a generation id.");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
for (var attempt = 1; attempt <= 60; attempt += 1) {
|
|
531
|
+
setStatus("Generating", "Polling " + generationId + " (" + attempt + "/60)", {
|
|
532
|
+
assetId: assetId,
|
|
533
|
+
mediaType: mediaType,
|
|
534
|
+
script: script,
|
|
535
|
+
voiceId: voice.id,
|
|
536
|
+
generationId: generationId,
|
|
537
|
+
lipsyncStarted: true,
|
|
538
|
+
});
|
|
539
|
+
var generation = await openai.callTool("generations_get", { id: generationId });
|
|
540
|
+
var statusText = generationStatusFrom(generation).toUpperCase();
|
|
541
|
+
var outputUrl = outputUrlFrom(generation);
|
|
542
|
+
if (statusText === "COMPLETED" || outputUrl) {
|
|
543
|
+
setStatus("Completed", outputUrl || generationId, {
|
|
544
|
+
assetId: assetId,
|
|
545
|
+
mediaType: mediaType,
|
|
546
|
+
script: script,
|
|
547
|
+
voiceId: voice.id,
|
|
548
|
+
generationId: generationId,
|
|
549
|
+
outputMediaUrl: outputUrl,
|
|
550
|
+
outputUrl: outputUrl,
|
|
551
|
+
lipsyncStarted: true,
|
|
552
|
+
modelContent:
|
|
553
|
+
"Sync lipsync completed. generationId: " +
|
|
554
|
+
generationId +
|
|
555
|
+
(outputUrl ? "; outputMediaUrl: " + outputUrl : ""),
|
|
556
|
+
});
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (statusText === "FAILED" || statusText === "CANCELED" || statusText === "CANCELLED") {
|
|
560
|
+
throw new Error("Sync generation " + generationId + " ended with status " + statusText + ".");
|
|
561
|
+
}
|
|
562
|
+
await delay(5000);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
throw new Error("Timed out polling Sync generation.");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function getDownloadUrl(fileId) {
|
|
569
|
+
var response = await openai.getFileDownloadUrl({ fileId: fileId });
|
|
570
|
+
var downloadUrl = downloadUrlFrom(response);
|
|
571
|
+
if (!downloadUrl) {
|
|
572
|
+
throw new Error("ChatGPT did not return a download URL for the selected file.");
|
|
573
|
+
}
|
|
574
|
+
return downloadUrl;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async function uploadToSync(fileRef) {
|
|
578
|
+
syncScriptInput();
|
|
579
|
+
var fileId = fileIdFrom(fileRef);
|
|
580
|
+
var fileName = fileRef.fileName || fileRef.file_name || "chatgpt-upload";
|
|
581
|
+
var mimeType = fileRef.mimeType || fileRef.mime_type || "";
|
|
582
|
+
if (!fileId) {
|
|
583
|
+
throw new Error("ChatGPT did not return a file id.");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
var mediaType = mediaTypeFrom(mimeType, fileName);
|
|
587
|
+
if (!mediaType) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
"The upload widget supports images and audio only. Attach videos in the ChatGPT composer, then ask Sync to create the lipsync."
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
setStatus("Preparing file", fileName, { fileId: fileId, fileName: fileName, mediaType: mediaType });
|
|
593
|
+
var downloadUrl = await getDownloadUrl(fileId);
|
|
594
|
+
setStatus("Uploading to Sync", fileName, { fileId: fileId, fileName: fileName, mediaType: mediaType });
|
|
595
|
+
|
|
596
|
+
var result = await openai.callTool("assets_upload", {
|
|
597
|
+
mediaType: mediaType,
|
|
598
|
+
file: {
|
|
599
|
+
download_url: downloadUrl,
|
|
600
|
+
file_id: fileId,
|
|
601
|
+
mime_type: mimeType || undefined,
|
|
602
|
+
file_name: fileName,
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
var assetId = assetIdFrom(result);
|
|
606
|
+
if (!assetId) {
|
|
607
|
+
throw new Error("Sync upload finished without an assetId.");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
var assetField = mediaType + "AssetId";
|
|
611
|
+
var modelContent = "Uploaded " + mediaType + " to Sync. " + assetField + ": " + assetId + ".";
|
|
612
|
+
var nextState = {
|
|
613
|
+
fileId: fileId,
|
|
614
|
+
fileName: fileName,
|
|
615
|
+
mimeType: mimeType,
|
|
616
|
+
mediaType: mediaType,
|
|
617
|
+
assetId: assetId,
|
|
618
|
+
assetField: assetField,
|
|
619
|
+
lipsyncStarted: false,
|
|
620
|
+
modelContent: modelContent,
|
|
621
|
+
privateContent: {
|
|
622
|
+
fileId: fileId,
|
|
623
|
+
fileName: fileName,
|
|
624
|
+
mimeType: mimeType,
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
if (mediaType === "image") nextState.imageIds = [fileId];
|
|
628
|
+
setStatus("Uploaded", assetField + " " + assetId, nextState);
|
|
629
|
+
|
|
630
|
+
await runLipsyncFlow(assetId, mediaType);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function runUploadedLipsync() {
|
|
634
|
+
syncScriptInput();
|
|
635
|
+
if (!script) {
|
|
636
|
+
setStatus("Script required", "Enter text for the image to say.");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (!state.assetId || state.mediaType !== "image") {
|
|
640
|
+
setStatus("Upload media first", "Choose or upload an image before running lipsync.");
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
setBusy(true);
|
|
646
|
+
await runLipsyncFlow(state.assetId, state.mediaType);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
setStatus("Lipsync failed", error && error.message ? error.message : String(error), {
|
|
649
|
+
lipsyncStarted: false,
|
|
650
|
+
});
|
|
651
|
+
} finally {
|
|
652
|
+
setBusy(false);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function chooseFromChatGpt() {
|
|
657
|
+
if (!canSelectFiles()) {
|
|
658
|
+
setStatus("Unavailable", "ChatGPT file selection is not available in this session.");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
setBusy(true);
|
|
663
|
+
setStatus("Choosing file", "Open the ChatGPT file picker.");
|
|
664
|
+
var files = await openai.selectFiles();
|
|
665
|
+
if (!files || files.length === 0) {
|
|
666
|
+
setStatus("Ready", "No file selected.");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
await uploadToSync(files[0]);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
setStatus("Upload failed", error && error.message ? error.message : String(error));
|
|
672
|
+
} finally {
|
|
673
|
+
setBusy(false);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async function uploadLocalFile(file) {
|
|
678
|
+
if (!canUploadLocal()) {
|
|
679
|
+
setStatus("Unavailable", "Local upload is not available in this session.");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
setBusy(true);
|
|
684
|
+
setStatus("Uploading to ChatGPT", file.name);
|
|
685
|
+
var response = await openai.uploadFile(file, { library: true });
|
|
686
|
+
var fileId = fileIdFrom(response);
|
|
687
|
+
if (!fileId) {
|
|
688
|
+
throw new Error("ChatGPT did not return a file id.");
|
|
689
|
+
}
|
|
690
|
+
await uploadToSync({
|
|
691
|
+
fileId: fileId,
|
|
692
|
+
fileName: file.name,
|
|
693
|
+
mimeType: file.type,
|
|
694
|
+
});
|
|
695
|
+
} catch (error) {
|
|
696
|
+
setStatus("Upload failed", error && error.message ? error.message : String(error));
|
|
697
|
+
} finally {
|
|
698
|
+
localInput.value = "";
|
|
699
|
+
setBusy(false);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
selectButton.addEventListener("click", chooseFromChatGpt);
|
|
704
|
+
localButton.addEventListener("click", function () {
|
|
705
|
+
localInput.click();
|
|
706
|
+
});
|
|
707
|
+
localInput.addEventListener("change", function (event) {
|
|
708
|
+
var file = event.target.files && event.target.files[0];
|
|
709
|
+
if (file) void uploadLocalFile(file);
|
|
710
|
+
});
|
|
711
|
+
scriptInput.addEventListener("input", syncScriptInput);
|
|
712
|
+
runButton.addEventListener("click", runUploadedLipsync);
|
|
713
|
+
window.addEventListener("openai:set_globals", function (event) {
|
|
714
|
+
openai = window.openai || openai;
|
|
715
|
+
syncBridgeInput(Object.assign({}, bridgeObject(openai), globalsFromEvent(event)));
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
scriptInput.value = script;
|
|
719
|
+
syncBridgeInput(openai);
|
|
720
|
+
updateHint();
|
|
721
|
+
if (!openai) {
|
|
722
|
+
setStatus("Unavailable", "Open this widget inside ChatGPT.");
|
|
723
|
+
} else {
|
|
724
|
+
setStatus(state.statusTitle || "Ready", state.statusDetail || "Waiting for media.", state);
|
|
725
|
+
}
|
|
726
|
+
setBusy(false);
|
|
727
|
+
})();
|
|
728
|
+
</script>
|
|
729
|
+
</body>
|
|
730
|
+
</html>
|
|
731
|
+
`.trim();
|
|
732
|
+
export function registerUploadWidgetResource(server) {
|
|
733
|
+
for (const uri of UPLOAD_WIDGET_RESOURCE_URIS) {
|
|
734
|
+
const version = /upload-widget-(v\d+)\.html$/.exec(uri)?.[1] ?? 'current';
|
|
735
|
+
server.registerResource(`sync-upload-widget-${version}`, uri, UPLOAD_WIDGET_RESOURCE_METADATA, async (requestedUri) => ({
|
|
736
|
+
contents: [
|
|
737
|
+
{
|
|
738
|
+
uri: requestedUri.toString(),
|
|
739
|
+
mimeType: MCP_APP_RESOURCE_MIME_TYPE,
|
|
740
|
+
text: UPLOAD_WIDGET_HTML,
|
|
741
|
+
_meta: UPLOAD_WIDGET_RESOURCE_META,
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
}));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
export function createUploadWidgetTool() {
|
|
748
|
+
return {
|
|
749
|
+
name: 'assets_open-upload-widget',
|
|
750
|
+
title: 'Open image/audio upload widget',
|
|
751
|
+
description: 'Display the Sync image/audio uploader in ChatGPT. The widget can stage a selected local image or audio file as a Sync asset and start a lipsync generation. It does not accept video files.',
|
|
752
|
+
inputSchema: {
|
|
753
|
+
requestedMediaType: z
|
|
754
|
+
.enum(['image', 'audio'])
|
|
755
|
+
.describe('Optional image or audio media type expected from the user. Video is invalid.')
|
|
756
|
+
.optional(),
|
|
757
|
+
},
|
|
758
|
+
outputSchema: uploadWidgetOutputSchema,
|
|
759
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
760
|
+
meta: {
|
|
761
|
+
ui: { resourceUri: UPLOAD_WIDGET_URI, visibility: ['model', 'app'] },
|
|
762
|
+
'openai/outputTemplate': UPLOAD_WIDGET_URI,
|
|
763
|
+
'openai/widgetAccessible': true,
|
|
764
|
+
'openai/toolInvocation/invoking': 'Opening Sync uploader...',
|
|
765
|
+
'openai/toolInvocation/invoked': 'Sync uploader ready.',
|
|
766
|
+
},
|
|
767
|
+
resultFormat: 'mcp',
|
|
768
|
+
handler: async (args) => {
|
|
769
|
+
const requestedMediaType = args.requestedMediaType === 'image' || args.requestedMediaType === 'audio'
|
|
770
|
+
? args.requestedMediaType
|
|
771
|
+
: undefined;
|
|
772
|
+
const structuredContent = {};
|
|
773
|
+
if (requestedMediaType) {
|
|
774
|
+
structuredContent.requestedMediaType = requestedMediaType;
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
structuredContent,
|
|
778
|
+
content: [
|
|
779
|
+
{
|
|
780
|
+
type: 'text',
|
|
781
|
+
text: 'Sync uploader opened. Select media in the widget; it will return a durable Sync assetId.',
|
|
782
|
+
},
|
|
783
|
+
],
|
|
784
|
+
_meta: structuredContent,
|
|
785
|
+
};
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
}
|