k-l-lambda commited on
Commit
310419f
·
1 Parent(s): 2fa4e1a

enhanced logs

Browse files
Files changed (1) hide show
  1. app.py +107 -8
app.py CHANGED
@@ -17,6 +17,8 @@ import os
17
  import re
18
  import time
19
  import json
 
 
20
 
21
  import gradio as gr
22
 
@@ -44,11 +46,58 @@ INSTRUMENTS = _STYLES['instruments']
44
  _GEN = None
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def get_generator ():
48
  '''Lazily build the (heavy) ONNX generator on first use.'''
49
  global _GEN
50
  if _GEN is None:
 
 
51
  _GEN = StreamingLilyletGenerator(MODEL_DIR, ASSET_DIR)
 
52
  return _GEN
53
 
54
 
@@ -103,6 +152,26 @@ def sync_prompt (composer, genre, instrument, current):
103
  return '\n'.join(lines)
104
 
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  def run_generation (prompt, measures, temperature, max_patches, seed, store, top_k=0, top_p=0.9):
107
  '''Streaming generate callback. Yields updates for (log, editor, file_list, store).
108
 
@@ -111,24 +180,48 @@ def run_generation (prompt, measures, temperature, max_patches, seed, store, top
111
 
112
  top_k / top_p have fixed defaults (no UI controls); pass them explicitly to override.
113
  '''
114
- gen = get_generator()
115
  meas = int(measures) if measures and int(measures) > 0 else None
116
  store = dict(store or {})
 
 
117
 
118
  raw = pretty = ''
119
- for raw, pretty, done in gen.generate_stream(
120
- prompt_text=prompt or '', max_patches=int(max_patches), temperature=float(temperature),
121
- top_k=int(top_k), top_p=float(top_p), measures=meas, seed=int(seed)):
122
- # stream: update log + editor, keep file list / store unchanged
123
- yield raw, pretty, gr.update(), store
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
  # finished: persist the document, refresh the file list, select the new entry
126
  label = OUTPUT_PREFIX + time.strftime('%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
127
  store[label] = pretty
128
  os.makedirs(OUTPUT_DIR, exist_ok=True)
129
- with open(os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl'), 'w', encoding='utf-8') as f:
 
130
  f.write(pretty)
131
- yield raw, pretty, gr.update(choices=list(store.keys()), value=label), store
 
132
 
133
 
134
  def load_file (label, store):
@@ -164,6 +257,12 @@ CUSTOM_CSS = '''
164
  white-space: nowrap;
165
  vertical-align: middle;
166
  }
 
 
 
 
 
 
167
  '''
168
 
169
 
 
17
  import re
18
  import time
19
  import json
20
+ import logging
21
+ from collections import deque
22
 
23
  import gradio as gr
24
 
 
46
  _GEN = None
47
 
48
 
49
+ # ---- system log capture ----------------------------------------------------
50
+ # A ring buffer that mirrors Python logging (lifecycle messages, warnings, and
51
+ # errors) into the Logs panel, alongside the streamed generation text.
52
+
53
+ LOG = logging.getLogger('lilyscript')
54
+
55
+
56
+ class _RingBufferHandler (logging.Handler):
57
+ '''Keep the most recent N log records as formatted strings.'''
58
+ def __init__ (self, capacity=400):
59
+ super().__init__()
60
+ self.records = deque(maxlen=capacity)
61
+
62
+ def emit (self, record):
63
+ try:
64
+ self.records.append(self.format(record))
65
+ except Exception:
66
+ pass
67
+
68
+ def text (self):
69
+ return '\n'.join(self.records)
70
+
71
+
72
+ _LOG_BUFFER = _RingBufferHandler()
73
+ _LOG_BUFFER.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%H:%M:%S'))
74
+
75
+
76
+ def _init_logging ():
77
+ '''Route app + library logs and Python warnings into the ring buffer (once),
78
+ and also echo to stderr so the terminal keeps a copy.'''
79
+ logging.captureWarnings(True)
80
+ root = logging.getLogger()
81
+ if _LOG_BUFFER not in root.handlers:
82
+ root.addHandler(_LOG_BUFFER)
83
+ stderr_h = logging.StreamHandler()
84
+ stderr_h.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%H:%M:%S'))
85
+ root.addHandler(stderr_h)
86
+ root.setLevel(logging.INFO)
87
+ LOG.setLevel(logging.INFO)
88
+
89
+
90
+ _init_logging()
91
+
92
+
93
  def get_generator ():
94
  '''Lazily build the (heavy) ONNX generator on first use.'''
95
  global _GEN
96
  if _GEN is None:
97
+ LOG.info('loading ONNX generator from %s ...', MODEL_DIR)
98
+ t0 = time.perf_counter()
99
  _GEN = StreamingLilyletGenerator(MODEL_DIR, ASSET_DIR)
100
+ LOG.info('generator ready (%.1fs)', time.perf_counter() - t0)
101
  return _GEN
102
 
103
 
 
152
  return '\n'.join(lines)
153
 
154
 
155
+ # Marker line written to the log buffer at the moment generation output begins.
156
+ # `_log_panel` replaces this marker with the live generation text, so the streamed
157
+ # output appears in true chronological position — after the "requested"/"ready"
158
+ # lines and before the later "timing"/"done" lines.
159
+ _GEN_MARKER = '⁣__GENERATION_OUTPUT__⁣'
160
+
161
+
162
+ def _log_panel (raw=''):
163
+ '''Render the Logs panel in chronological order: the captured system log,
164
+ with the generation-output marker (if present) expanded to the live text.'''
165
+ sys_log = _LOG_BUFFER.text()
166
+ if _GEN_MARKER in sys_log:
167
+ block = '--- generation output ---\n' + raw if raw else '--- generation output ---'
168
+ return sys_log.replace(_GEN_MARKER, block)
169
+ # no marker yet (e.g. before generation): fall back to appending raw
170
+ if raw:
171
+ return (sys_log + '\n' if sys_log else '') + '--- generation output ---\n' + raw
172
+ return sys_log
173
+
174
+
175
  def run_generation (prompt, measures, temperature, max_patches, seed, store, top_k=0, top_p=0.9):
176
  '''Streaming generate callback. Yields updates for (log, editor, file_list, store).
177
 
 
180
 
181
  top_k / top_p have fixed defaults (no UI controls); pass them explicitly to override.
182
  '''
 
183
  meas = int(measures) if measures and int(measures) > 0 else None
184
  store = dict(store or {})
185
+ LOG.info('generation requested: measures=%s temperature=%s max_patches=%s seed=%s',
186
+ meas, temperature, max_patches, seed)
187
 
188
  raw = pretty = ''
189
+ n_yields = 0
190
+ t0 = time.perf_counter()
191
+ try:
192
+ gen = get_generator()
193
+ # drop a marker in the log timeline; _log_panel expands it to the live output,
194
+ # so subsequent log lines (timing/done) land *after* the generation text.
195
+ LOG.info(_GEN_MARKER)
196
+ for raw, pretty, done in gen.generate_stream(
197
+ prompt_text=prompt or '', max_patches=int(max_patches), temperature=float(temperature),
198
+ top_k=int(top_k), top_p=float(top_p), measures=meas, seed=int(seed)):
199
+ if not done:
200
+ n_yields += 1
201
+ # stream: update log (chronological system log + inline output) + editor
202
+ yield _log_panel(raw), pretty, gr.update(), store
203
+ except Exception as e:
204
+ LOG.exception('generation failed: %s', e)
205
+ yield _log_panel(raw), pretty, gr.update(), store
206
+ return
207
+
208
+ # timing: the stream yields once for prefill + once per generated patch, so the
209
+ # patch count is the non-done yields minus that initial prefill yield.
210
+ elapsed = time.perf_counter() - t0
211
+ n_patches = max(0, n_yields - 1)
212
+ per_patch = (elapsed / n_patches) if n_patches else 0.0
213
+ LOG.info('timing: %.2fs total, %d patches, %.3fs/patch (%.1f patches/s)',
214
+ elapsed, n_patches, per_patch, (n_patches / elapsed if elapsed else 0.0))
215
 
216
  # finished: persist the document, refresh the file list, select the new entry
217
  label = OUTPUT_PREFIX + time.strftime('%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
218
  store[label] = pretty
219
  os.makedirs(OUTPUT_DIR, exist_ok=True)
220
+ out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
221
+ with open(out_path, 'w', encoding='utf-8') as f:
222
  f.write(pretty)
223
+ LOG.info('generation done: %d chars -> %s', len(pretty), os.path.basename(out_path))
224
+ yield _log_panel(raw), pretty, gr.update(choices=list(store.keys()), value=label), store
225
 
226
 
227
  def load_file (label, store):
 
257
  white-space: nowrap;
258
  vertical-align: middle;
259
  }
260
+ /* Score List: fixed height with an auto scrollbar, matching the editor's
261
+ 18-line viewport (gr.Code lines=18 ≈ 266px). */
262
+ .score-list {
263
+ max-height: 324px;
264
+ overflow-y: auto;
265
+ }
266
  '''
267
 
268