forked from flow/vue3js-app-proposal-for-sdk-claude
Add audiowidget endpoint: Blueprint renders audiowidget.tmpl, PaneCP fetches it on synthesis completion
This commit is contained in:
parent
06c077b7f8
commit
be338c2de0
12
__init__.py
12
__init__.py
@ -1,4 +1,5 @@
|
|||||||
from flask import Blueprint, render_template, request
|
from flask import Blueprint, render_template, request
|
||||||
|
from jinja2 import TemplateNotFound
|
||||||
|
|
||||||
blueprint = Blueprint(
|
blueprint = Blueprint(
|
||||||
'vue3_neusik',
|
'vue3_neusik',
|
||||||
@ -14,3 +15,14 @@ def index():
|
|||||||
'vue3_neusik/index.html',
|
'vue3_neusik/index.html',
|
||||||
import_on_load='true' if request.args.get('import') == '1' else 'false',
|
import_on_load='true' if request.args.get('import') == '1' else 'false',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@blueprint.route('/audiowidget', methods=['GET'])
|
||||||
|
def audiowidget():
|
||||||
|
try:
|
||||||
|
return render_template(
|
||||||
|
'vue3_neusik/audiowidget.tmpl',
|
||||||
|
result_url=request.args.get('result_url', ''),
|
||||||
|
errors=request.args.get('errors', ''),
|
||||||
|
)
|
||||||
|
except TemplateNotFound:
|
||||||
|
return '', 204
|
||||||
|
|||||||
@ -44,3 +44,12 @@ export async function fetchStatus(credentials) {
|
|||||||
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchAudioWidget(resultUrl, errors) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (resultUrl) params.set('result_url', resultUrl);
|
||||||
|
if (errors) params.set('errors', errors);
|
||||||
|
const res = await fetch(`${U.audiowidget}?${params}`);
|
||||||
|
if (!res.ok || res.status === 204) return '';
|
||||||
|
return res.text();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { h, ref } from 'vue';
|
import { h, ref, watch } from 'vue';
|
||||||
import { fetchScoreText, putScoreText, URLS } from '../api.js';
|
import { fetchScoreText, putScoreText, fetchAudioWidget, URLS } from '../api.js';
|
||||||
import { patchScore } from '../exporter.js';
|
import { patchScore } from '../exporter.js';
|
||||||
import { StatusPoller } from './StatusPoller.js';
|
import { StatusPoller } from './StatusPoller.js';
|
||||||
|
|
||||||
@ -40,6 +40,18 @@ export const PaneCP = {
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const exporting = ref(false);
|
const exporting = ref(false);
|
||||||
const exportError = ref('');
|
const exportError = ref('');
|
||||||
|
const audioWidgetHtml = ref('');
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.store.synthesisStatus,
|
||||||
|
async (s) => {
|
||||||
|
if (!s?.frozen) { audioWidgetHtml.value = ''; return; }
|
||||||
|
audioWidgetHtml.value = await fetchAudioWidget(
|
||||||
|
s.file_accomplished ? URLS.result : null,
|
||||||
|
s.errors ?? null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
async function doExport() {
|
async function doExport() {
|
||||||
exportError.value = '';
|
exportError.value = '';
|
||||||
@ -118,18 +130,10 @@ export const PaneCP = {
|
|||||||
store.synthesisStatus && !store.synthesisStatus.frozen
|
store.synthesisStatus && !store.synthesisStatus.frozen
|
||||||
? h(StatusPoller, { store }) : null,
|
? h(StatusPoller, { store }) : null,
|
||||||
|
|
||||||
// Synthesis error
|
// Audio widget (rendered server-side from audiowidget.tmpl)
|
||||||
store.synthesisStatus?.frozen && store.synthesisStatus?.errors
|
audioWidgetHtml.value
|
||||||
? h('div', { class: 'se-error', style: 'margin-top:0.5rem' },
|
? h('div', { innerHTML: audioWidgetHtml.value, style: 'margin-top:0.5rem' })
|
||||||
store.synthesisStatus.errors) : null,
|
: null,
|
||||||
|
|
||||||
// Audio result
|
|
||||||
store.synthesisStatus?.file_accomplished
|
|
||||||
? h('audio', {
|
|
||||||
controls: true,
|
|
||||||
src: URLS.result,
|
|
||||||
style: 'display:block;width:100%;margin-top:0.5rem',
|
|
||||||
}) : null,
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
18
templates/vue3_neusik/audiowidget.tmpl.stub
Normal file
18
templates/vue3_neusik/audiowidget.tmpl.stub
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{# audiowidget.tmpl.stub — copy to audiowidget.tmpl in the same directory to activate.
|
||||||
|
Customise to match your site's look and feel.
|
||||||
|
|
||||||
|
Context variables (passed as query params by the score editor):
|
||||||
|
result_url – URL of the rendered MP3, or empty string
|
||||||
|
errors – synthesis error message, or empty string
|
||||||
|
#}
|
||||||
|
{% if errors %}
|
||||||
|
<div class="se-error">{{ errors }}</div>
|
||||||
|
{% elif result_url %}
|
||||||
|
<figure class="audio-result">
|
||||||
|
<figcaption>Rendered result</figcaption>
|
||||||
|
<audio controls preload="metadata" style="display:block;width:100%">
|
||||||
|
<source src="{{ result_url }}" type="audio/mpeg">
|
||||||
|
<a href="{{ result_url }}">Download MP3</a>
|
||||||
|
</audio>
|
||||||
|
</figure>
|
||||||
|
{% endif %}
|
||||||
@ -14,8 +14,9 @@ window.NEUSICIAN_URLS = {
|
|||||||
astlog: "{{ url_for('astlog') }}",
|
astlog: "{{ url_for('astlog') }}",
|
||||||
score: "{{ url_for('score') }}",
|
score: "{{ url_for('score') }}",
|
||||||
status: "{{ url_for('statusjson') }}",
|
status: "{{ url_for('statusjson') }}",
|
||||||
result: "{{ url_for('send_audio_generated') }}",
|
result: "{{ url_for('send_audio_generated') }}",
|
||||||
submit: "{{ url_for('public-yaml-acceptor') }}"
|
submit: "{{ url_for('public-yaml-acceptor') }}",
|
||||||
|
audiowidget: "{{ url_for('vue3_neusik.audiowidget') }}"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user