k-l-lambda commited on
Commit
6925b7f
·
1 Parent(s): a6b3d77

fixed share link

Browse files
Files changed (1) hide show
  1. app.py +65 -14
app.py CHANGED
@@ -183,11 +183,15 @@ def load_outputs ():
183
  '''Read previously-generated .lyl files from the outputs dir into a
184
  {label: text} dict, so past session outputs survive a server restart.'''
185
  store = {}
186
- if os.path.isdir(OUTPUT_DIR):
187
- for name in sorted(os.listdir(OUTPUT_DIR)):
188
- if name.endswith('.lyl'):
189
- with open(os.path.join(OUTPUT_DIR, name), encoding='utf-8') as f:
190
- store[OUTPUT_PREFIX + name[:-4]] = f.read()
 
 
 
 
191
  return store
192
 
193
 
@@ -196,6 +200,19 @@ def load_library ():
196
  return {**load_examples(), **load_outputs()}
197
 
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  # A managed style line in the new `--styles-in-comments` format: a leading `%<value>`
200
  # comment carrying period / composer / instrumentation (one per line). `sync_prompt`
201
  # regenerates these from the dropdowns; any other line the user typed is preserved.
@@ -312,11 +329,26 @@ def run_generation (prompt, measures, temperature, max_patches, seed, store, top
312
  # finished: persist the document, refresh the file list, select the new entry
313
  label = OUTPUT_PREFIX + time.strftime('%Y%m%d_%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
314
  store[label] = pretty
315
- os.makedirs(OUTPUT_DIR, exist_ok=True)
316
  out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
317
- with open(out_path, 'w', encoding='utf-8') as f:
318
- f.write(pretty)
319
- LOG.info('generation done: %d chars -> %s', len(pretty), os.path.basename(out_path))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  # randomize the seed slider for the next run (the file above already used the
321
  # seed this generation ran with, so naming is unaffected)
322
  next_seed = random.randint(0, 2147483647)
@@ -505,7 +537,7 @@ function (value) {
505
  # <textarea> + document.execCommand('copy'), which works from a user gesture.
506
  _JS_SHARE = '''
507
  function () {
508
- const url = location.href;
509
  const flash = (msg, ok) => {
510
  const btn = document.getElementById('ls-share-btn');
511
  if (!btn) return;
@@ -522,11 +554,26 @@ function () {
522
  clearTimeout(hint._t);
523
  hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
524
  };
525
- // No score selected yet -> no #score= hash; the bare URL isn't a useful share link.
526
- if (!/(?:^#|&|#)score=/.test(location.hash || '')) {
527
- flash('Select a score first', false);
528
- return [];
 
 
 
 
 
 
 
 
 
 
 
 
529
  }
 
 
 
530
  const fallback = () => {
531
  try {
532
  const ta = document.createElement('textarea');
@@ -872,6 +919,10 @@ def build_ui ():
872
  # ---- wiring ----
873
  # mount the score player once the page (and LilyScore) is ready
874
  demo.load(None, None, None, js=_JS_HELPERS)
 
 
 
 
875
 
876
  # style dropdowns -> keep the metadata-prompt text box in sync
877
  for field in (composer, period, genre):
 
183
  '''Read previously-generated .lyl files from the outputs dir into a
184
  {label: text} dict, so past session outputs survive a server restart.'''
185
  store = {}
186
+ exists = os.path.isdir(OUTPUT_DIR)
187
+ names = sorted(os.listdir(OUTPUT_DIR)) if exists else []
188
+ lyls = [n for n in names if n.endswith('.lyl')]
189
+ LOG.info('load_outputs: OUTPUT_DIR=%s exists=%s total_entries=%d lyl_files=%d',
190
+ OUTPUT_DIR, exists, len(names), len(lyls))
191
+ if exists:
192
+ for name in lyls:
193
+ with open(os.path.join(OUTPUT_DIR, name), encoding='utf-8') as f:
194
+ store[OUTPUT_PREFIX + name[:-4]] = f.read()
195
  return store
196
 
197
 
 
200
  return {**load_examples(), **load_outputs()}
201
 
202
 
203
+ def refresh_library ():
204
+ '''Re-read the library from disk and return updates for (store, file_list).
205
+
206
+ Wired to demo.load so EVERY new session / page refresh picks up outputs that
207
+ were generated after server boot. Without this, store=gr.State(examples) and the
208
+ Radio's choices are frozen at the boot-time snapshot, so a refresh drops any
209
+ freshly-generated score from the Score List even when it persisted to disk.'''
210
+ lib = load_library()
211
+ LOG.info('refresh_library: %d entries (%d examples + %d outputs)',
212
+ len(lib), len(load_examples()), len(load_outputs()))
213
+ return lib, gr.update(choices=list(lib.keys()))
214
+
215
+
216
  # A managed style line in the new `--styles-in-comments` format: a leading `%<value>`
217
  # comment carrying period / composer / instrumentation (one per line). `sync_prompt`
218
  # regenerates these from the dropdowns; any other line the user typed is preserved.
 
329
  # finished: persist the document, refresh the file list, select the new entry
330
  label = OUTPUT_PREFIX + time.strftime('%Y%m%d_%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
331
  store[label] = pretty
 
332
  out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
333
+ # Persist to disk so the output survives a page refresh / server restart. On some
334
+ # hosts (e.g. a ModelScope studio) the project dir may be read-only or the
335
+ # container's filesystem ephemeral, so a write can fail or silently not persist —
336
+ # log the outcome (and don't let a write error kill the final yield, which would
337
+ # leave the new entry out of the Score List even in-session).
338
+ saved = False
339
+ try:
340
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
341
+ with open(out_path, 'w', encoding='utf-8') as f:
342
+ f.write(pretty)
343
+ size = os.path.getsize(out_path)
344
+ saved = os.path.isfile(out_path)
345
+ LOG.info('generation done: %d chars -> %s (saved=%s, on_disk_size=%d, dir_writable=%s)',
346
+ len(pretty), out_path, saved, size, os.access(OUTPUT_DIR, os.W_OK))
347
+ # list what's actually on disk right after the write, to catch ephemeral-FS cases
348
+ on_disk = [n for n in os.listdir(OUTPUT_DIR) if n.endswith('.lyl')]
349
+ LOG.info('outputs dir now holds %d .lyl file(s): %s', len(on_disk), on_disk)
350
+ except Exception as e:
351
+ LOG.exception('FAILED to persist generated score to %s: %s', out_path, e)
352
  # randomize the seed slider for the next run (the file above already used the
353
  # seed this generation ran with, so naming is unaffected)
354
  next_seed = random.randint(0, 2147483647)
 
537
  # <textarea> + document.execCommand('copy'), which works from a user gesture.
538
  _JS_SHARE = '''
539
  function () {
540
+ const stripPrefix = (s) => (s || '').replace(/^\\s*[\\u{1F4C4}\\u2728]\\s*/u, '').trim();
541
  const flash = (msg, ok) => {
542
  const btn = document.getElementById('ls-share-btn');
543
  if (!btn) return;
 
554
  clearTimeout(hint._t);
555
  hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
556
  };
557
+ // Determine the selected score. Prefer the live #score= hash, but fall back to the
558
+ // currently-checked Score List radio: when a fresh generation auto-selects its new
559
+ // file, that happens via Gradio setting the Radio value (NOT a user click), so the
560
+ // js-only select listener never fires and the hash is stale/empty. Reading the
561
+ // checked radio here covers both generation-selection and click-selection, and we
562
+ // (re)write the hash so the copied URL always deep-links to the right score.
563
+ let name = '';
564
+ const m = (location.hash || '').match(/(?:^#|&)score=([^&]*)/);
565
+ if (m) { try { name = decodeURIComponent(m[1]); } catch (e) { name = ''; } }
566
+ if (!name) {
567
+ const checked = document.querySelector('.score-list input:checked');
568
+ if (checked) {
569
+ const lab = checked.closest('label');
570
+ const span = lab && lab.querySelector('span');
571
+ name = stripPrefix(span ? span.textContent : (lab ? lab.textContent : ''));
572
+ }
573
  }
574
+ if (!name) { flash('Select a score first', false); return []; }
575
+ try { history.replaceState(null, '', '#score=' + encodeURIComponent(name)); } catch (e) {}
576
+ const url = location.href;
577
  const fallback = () => {
578
  try {
579
  const ta = document.createElement('textarea');
 
919
  # ---- wiring ----
920
  # mount the score player once the page (and LilyScore) is ready
921
  demo.load(None, None, None, js=_JS_HELPERS)
922
+ # re-read the library from disk on every new session / refresh, so outputs
923
+ # generated after server boot still appear in the Score List (store + Radio
924
+ # are otherwise frozen at the boot-time snapshot — see refresh_library).
925
+ demo.load(refresh_library, None, outputs=[store, file_list])
926
 
927
  # style dropdowns -> keep the metadata-prompt text box in sync
928
  for field in (composer, period, genre):