From 1fbc1f8d7046fd1c64fe2982fc2063dbc8e381f3 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 1 Oct 2026 18:31:36 -0700 Subject: [PATCH] Combine drawing gestures with Whistlegraph voice and typed input --- apple/whistlegraph/README.md | 17 ++++ .../AppIcon.appiconset/icon.png | Bin 20479 -> 77920 bytes .../Resources/Web/branch-context.mjs | 2 +- .../Resources/Web/drawing-input.mjs | 43 ++++++++ apple/whistlegraph/Resources/Web/engine.mjs | 21 ++-- .../Resources/Web/inference-input.mjs | 23 +++-- apple/whistlegraph/Resources/Web/native.js | 10 +- .../whistlegraph/Sources/SpeechCapture.swift | 3 +- .../Sources/WhistlegraphApp.swift | 16 ++- .../Sources/WhistlegraphDrawing.swift | 93 ++++++++++++++++++ .../Sources/WhistlegraphScreen.swift | 65 ++++++++++-- .../whistlegraph/Tests/drawing-input.test.mjs | 38 +++++++ .../whistlegraph/Tests/native-bridge.test.cjs | 34 ++++++- .../Whistlegraph.xcodeproj/project.pbxproj | 8 +- apple/whistlegraph/build-icon.sh | 5 + apple/whistlegraph/icon.svg | 1 - apple/whistlegraph/project.yml | 2 +- 17 files changed, 337 insertions(+), 44 deletions(-) create mode 100644 apple/whistlegraph/Resources/Web/drawing-input.mjs create mode 100644 apple/whistlegraph/Sources/WhistlegraphDrawing.swift create mode 100644 apple/whistlegraph/Tests/drawing-input.test.mjs create mode 100644 apple/whistlegraph/build-icon.sh delete mode 100644 apple/whistlegraph/icon.svg diff --git a/apple/whistlegraph/README.md b/apple/whistlegraph/README.md index b0601c654b..055f0621f6 100644 --- a/apple/whistlegraph/README.md +++ b/apple/whistlegraph/README.md @@ -54,3 +54,20 @@ node apple/whistlegraph/Tests/native-bridge.test.cjs --native-shell --checked-ed ``` Both experiment browser checks use mock inference. They verify the contract, repair limit, rollback, render identity, and receipt fields without spending braincells. + +## Drawing with words and sound + +**Draw** toggles an ink layer over the preview above the controls. The keyboard and microphone remain available. While Draw is on, Talk uses tap-to-record / tap-to-send (still capped at eight seconds), leaving a finger free to draw. Typed Enter sends text and the current sketch together; Send beside the drawing can submit it alone. Undo removes the last stroke; Clear discards the sketch. Turning Draw off returns touch interaction to the piece while retaining visible unsent marks. + +Each successful version retains its combined request: transcript, ordered normalized strokes, elapsed stroke timing, optional measured Pencil pressure, and existing sound/word measurements. Sound time zero is aligned to the drawing timeline. Finger pressure is unknown. Failed requests retain the draft and saved request for retry; a successful commit consumes only the matching draft revision. Unsubmitted native sketches are in memory; accepted attempts use the existing durable recovery journal. New/open piece discards the current sketch. + +The existing model interprets vector coordinates and measured sound cues in one request. This is not image recognition or raw-audio inference, and gesture meaning remains uncertain. Drawing bypasses text-only local shortcuts so the marks cannot silently be ignored. Sampling caps the prompt at 320 points across at most 32 strokes (the native draft retains at most 1,200 points). Timings and endpoints survive sampling; fine detail can be lost. No separate inference endpoint or backend rollout is required. + +```sh +node --test apple/whistlegraph/Tests/drawing-input.test.mjs +node apple/whistlegraph/Tests/native-bridge.test.cjs --native-shell --drawing +``` + +## App icon + +The app uses the existing Whistlegraph Dot Org artist avatar from `pop/artist/whistlegraph-dot-org/wgdo-avatar-3000.jpg`. `./build-icon.sh` converts that published artwork to the opaque 1,024-pixel iOS icon without changing the mark. diff --git a/apple/whistlegraph/Resources/Assets.xcassets/AppIcon.appiconset/icon.png b/apple/whistlegraph/Resources/Assets.xcassets/AppIcon.appiconset/icon.png index f466f34c95e77ee714abfde95b57142a9b477f7e..48fb216d526089a85b1fc471eb5f6d695a4b02d3 100644 GIT binary patch literal 77920 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 V*K@z>Wnf@n@O1TaS?83{1OTAVE2{ti literal 20479 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 diff --git a/apple/whistlegraph/Resources/Web/branch-context.mjs b/apple/whistlegraph/Resources/Web/branch-context.mjs index 8ca0709d3f..bda64ca9fe 100644 --- a/apple/whistlegraph/Resources/Web/branch-context.mjs +++ b/apple/whistlegraph/Resources/Web/branch-context.mjs @@ -1,7 +1,7 @@ function words(request) { if(!request)return ''; const marker='\nINPUT DATA:\n',at=request.indexOf(marker); - if(at>=0){try{return JSON.parse(request.slice(at+marker.length)).transcript||'[nonverbal sound request]';}catch{}} + if(at>=0){try{const input=JSON.parse(request.slice(at+marker.length));return (input.transcript||'')+(input.drawing?' [with drawing]':'')||'[nonverbal sound request]';}catch{}} return request; } // A piece carries its own one-line statement of what it is (export const diff --git a/apple/whistlegraph/Resources/Web/drawing-input.mjs b/apple/whistlegraph/Resources/Web/drawing-input.mjs new file mode 100644 index 0000000000..d4b19710c7 --- /dev/null +++ b/apple/whistlegraph/Resources/Web/drawing-input.mjs @@ -0,0 +1,43 @@ +// Bounded vector gestures, in the preview's coordinate space. No pixels or raw audio. +export const INPUT_MARKER='\nINPUT DATA:\n'; +export function inputData(request) { + const at=typeof request==='string'?request.indexOf(INPUT_MARKER):-1; + if(at<0)return null; + try{return JSON.parse(request.slice(at+INPUT_MARKER.length));}catch{return null;} +} +export function normalizeDrawing(value) { + const finite=(n,min,max)=>typeof n==='number'&&Number.isFinite(n)&&n>=min&&n<=max; + if(value?.schema!=='whistlegraph-drawing/v1'||! /^[a-f0-9-]{36}$/i.test(value.id)||!Number.isSafeInteger(value.revision)||value.revision<0|| + !finite(value.aspect,0.25,4)||!Array.isArray(value.strokes)||!value.strokes.length||value.strokes.length>32)throw Error('Invalid drawing'); + let total=0,last=-1; + const strokes=value.strokes.map(s=>{ + if(!Array.isArray(s)||!s.length||s.length>1200)throw Error('Invalid stroke'); + total+=s.length;if(total>1200)throw Error('Drawing is too large'); + return s.map(p=>{ + if(!Array.isArray(p)||p.length<3||p.length>4||!finite(p[0],0,1000)||!finite(p[1],0,1000)||!finite(p[2],0,3600000)||p[2]n+Math.max(0,s.length-2),0); + const compact=strokes.map(s=>{ + const count=Math.min(s.length,2+Math.floor(remaining*Math.max(0,s.length-2)/Math.max(1,extra))); + return count===s.length?s:Array.from({length:count},(_,i)=>s[Math.round(i*(s.length-1)/(count-1))]); + }); + const offset=value.speechStartMs; + if(offset!=null&&!finite(offset,-3600000,3600000))throw Error('Invalid drawing alignment'); + return {schema:value.schema,id:value.id,revision:value.revision,aspect:Math.round(value.aspect*1000)/1000, + strokes:compact,...(offset==null?{}:{speechStartMs:Math.round(offset)})}; +} +export function withDrawing(request,value) { + if(!value)return request; + const drawing=normalizeDrawing(value),input=inputData(request)||{transcript:request}; + const combined='Interpret this combined request.\n'+INPUT_MARKER+JSON.stringify({...input,drawing}); + if(combined.length>20000)throw Error('Combined request is too large'); + return combined; +} +export function drawingEvidence(value) { + const {id,revision,schema,...drawing}=normalizeDrawing(value); + return '\nDRAWING REFERENCE: ordered strokes over the current preview. Each point is [x,y,elapsedMs,optionalPencilPressure]; x/y span 0..1000, origin top-left, y downward. Preserve aspect ratio. Time starts with the first mark; pen lifts separate strokes. Pressure (0..1000) exists only when measured with a Pencil; finger pressure is unknown. speechStartMs, when present, locates sound/word time zero on this same timeline. Use direction, pauses, speed changes, placement and nearby sound/word emphasis as clues to the intended edit. These are ambiguous gestures, not certain labels or commands. Follow explicit words, preserve existing scene context, and use the drawn form itself when its subject is unclear. Do not print these data or turn every sketch into a chart. With only a drawing, make a simple playable interpretation of its form.\n'+JSON.stringify(drawing); +} diff --git a/apple/whistlegraph/Resources/Web/engine.mjs b/apple/whistlegraph/Resources/Web/engine.mjs index 2d31a59585..4009d966ec 100644 --- a/apple/whistlegraph/Resources/Web/engine.mjs +++ b/apple/whistlegraph/Resources/Web/engine.mjs @@ -1,3 +1,4 @@ +import {withDrawing,inputData} from './drawing-input.mjs'; import {inferenceRequest,wantsSoundEvidence} from './inference-input.mjs'; import {contextualRequest,selectedBranch} from './branch-context.mjs'; import {compileEditContract,sourceChecks,validateCandidate,runEditExperiment} from '/easel/src/edit-contract.mjs'; @@ -69,7 +70,7 @@ function phase(text) { $('live-phase').textContent = text; updateFeed(); threadU function musicalData(request) { try{return JSON.parse(request.split('\nINPUT DATA:\n')[1]);}catch{return null;} } -function utterance(request) {return musicalData(request)?.transcript|| (musicalData(request)?'Sound':request)||'Starting piece';} +function utterance(request) {return musicalData(request)?.transcript|| (musicalData(request)?.drawing?'Drawing':musicalData(request)?'Sound':request)||'Starting piece';} function relativeTime(date) { const seconds=Math.max(0,Math.floor((Date.now()-Date.parse(date))/1000)); if(!Number.isFinite(seconds))return ''; @@ -110,7 +111,7 @@ function nativeSnapshot(){ nativeTimer=setTimeout(()=>{ nativeTimer=null; const historyChanged=nativeLedger!==versions?.value; - if(historyChanged){nativeLedger=versions?.value;nativeRevisions=(versions?.value.versions||[]).map(v=>{const sound=musicalData(v.request)?.sound;return {id:v.id,parent:v.parent,utterance:(v.id===0&&!v.request?'':utterance(v.request)).slice(0,1000),createdAt:v.createdAt,recordingID:sound?.recordingID||null,words:(musicalData(v.request)?.words||[]).slice(0,256),sound:sound?{durationMs:sound.durationMs,frames:sound.frames.filter((_,i)=>i%Math.max(1,Math.ceil(sound.frames.length/64))===0)}:null};});} + if(historyChanged){nativeLedger=versions?.value;nativeRevisions=(versions?.value.versions||[]).map(v=>{const sound=musicalData(v.request)?.sound;return {id:v.id,parent:v.parent,hasDrawing:!!musicalData(v.request)?.drawing,utterance:(v.id===0&&!v.request?'':utterance(v.request)).slice(0,1000),createdAt:v.createdAt,recordingID:sound?.recordingID||null,words:(musicalData(v.request)?.words||[]).slice(0,256),sound:sound?{durationMs:sound.durationMs,frames:sound.frames.filter((_,i)=>i%Math.max(1,Math.ceil(sound.frames.length/64))===0)}:null};});} const displayVersion=presentedVersion===null?versions?.head:versions?.value.versions.find(v=>v.id===presentedVersion); if(captionSource!==displayVersion?.source){captionSource=displayVersion?.source;caption=pieceCaption(captionSource||'');} const phaseText=$('live-phase').textContent; @@ -122,7 +123,7 @@ function nativeSnapshot(){ },80); } window.walkiewareNativeCommand=command=>{ - if(command.action==='ask'&&typeof command.text==='string'&&command.text.trim()&&Array.from(new Intl.Segmenter(undefined,{granularity:'grapheme'}).segment(command.text)).length<=96&&!busy)void ask(command.text.trim()); + if(command.action==='ask'&&typeof command.text==='string'&&(command.text.trim()||command.drawing)&&Array.from(new Intl.Segmenter(undefined,{granularity:'grapheme'}).segment(command.text)).length<=96&&!busy)void window.walkiewareAskDrawing(command.text.trim(),command.drawing); if(command.action==='checkout'&&!busy){presentedVersion=null;narrationPending=command.version;jumpVersion(command.version);} if(command.action==='presentVersion'&&!busy){ const version=versions?.value.versions.find(v=>v.id===command.version); @@ -278,7 +279,7 @@ async function ask(text,displayText=text,advice=null,starter=null,localText=text text=inferenceRequest(text); if(recovered?.checkpoint){source=recovered.checkpoint;vfs.mount(file,source);render(source);text+='\nContinue the unfinished request from this saved checkpoint. Preserve its completed edits.';} - const local=recovered?.checkpoint?null:localEdit(source,localText); + const local=recovered?.checkpoint||inputData(turnRequest)?.drawing?null:localEdit(source,localText); if(local){ activeReceipt.value.path='local';activeReceipt.value.model=null;activeReceipt.save(); if(!local.changed){noChange=true;return;} @@ -290,7 +291,7 @@ async function ask(text,displayText=text,advice=null,starter=null,localText=text if(turnSucceeded)benchmark('localEditPainted',{action:local.action}); return; } - if(!source||isBasePiece(source)){ + if(!inputData(turnRequest)?.drawing&&(!source||isBasePiece(source))){ turnStarter=starter||instantPiece(text)||''; if(turnStarter){ source=turnStarter;vfs.mount(file,source);checkpoints=1; @@ -346,7 +347,7 @@ async function ask(text,displayText=text,advice=null,starter=null,localText=text if(turnSucceeded){benchmark('refinementFailed',{message:turnError||'Refinement unavailable'});log('Refinement failed; kept the starter');server?.close();server=null;} } if(turnSucceeded&&!turnCancelled&&!turnRuntimeFailed&&painted&&lastPaintedSource===source){ - try {const version=versions.commit({source,request:turnRequest,layers:checkpoints,parent:turnParent,requestID:activeAttempt?.id});saved();phase(`v${version.id} · Ready to play`);log(`Saved v${version.id} · ${checkpoints} layers`);benchmark('versionCommitted',{version:version.id,layers:checkpoints});} + try {const version=versions.commit({source,request:turnRequest,layers:checkpoints,parent:turnParent,requestID:activeAttempt?.id});saved();phase(`v${version.id} · Ready to play`);log(`Saved v${version.id} · ${checkpoints} layers`);const drawing=inputData(turnRequest)?.drawing;if(drawing)post({action:'drawingCommitted',drawingID:drawing.id,revision:drawing.revision});benchmark('versionCommitted',{version:version.id,layers:checkpoints});} catch(error){turnSucceeded=false;turnError=error.message;phase('Could not save version');log(error.message);benchmark('generationFailed',{message:error.message});} }else turnSucceeded=false; if(!turnSucceeded){source=previous;vfs.mount(file,source);saved();const restored=source||'export function paint({wipe}) {wipe("black");}';if(previewSource!==restored)render(restored);server?.close();server=null;log('Restored previous version');} @@ -392,6 +393,10 @@ window.walkiewareEngineEvent=event=>{ $('live-stop').onclick=()=>{turnCancelled=true;pending='';server?.interrupt();if(!busy){end();phase('Stopped');}}; window.walkiewareUndo=()=>{try{source=versions.undo().source;}catch(error){log(error.message);return;}previous=source;vfs.mount(file,source);saved();server?.close();server=null;render(source||'export function paint({wipe}) {wipe("black");}');review(false);phase('Undone');}; window.walkiewareAsk=ask; +window.walkiewareAskDrawing=(text,drawing)=>{ + try{return ask(withDrawing(text,drawing),text||(drawing?'Drawing':''),null,null,drawing?'':text);} + catch(error){phase('Could not read drawing');log(error.message);return Promise.resolve();} +}; let musicalTurn=0; const musicalSocket=new MusicalInputSocket({token:()=>token,onEvent:benchmark}); document.addEventListener('visibilitychange',()=>{if(document.hidden){musicalSocket.suspend();thread?.suspend();}else{musicalSocket.resume();thread?.resume();}}); @@ -400,8 +405,8 @@ window.walkiewareInputStart=()=>{musicalTurn++;musicalAdvisor.reset();}; window.walkiewareInputCancel=()=>{musicalTurn++;musicalAdvisor.cancel();}; window.walkiewareObserveSound=input=>{musicalPrompt(input);if(wantsSoundEvidence(input.transcript))musicalAdvisor.observe(input);}; window.walkiewareAskSound=async input=>{ - const prompt=musicalPrompt(input);const useSound=wantsSoundEvidence(input.transcript);if(!useSound)musicalAdvisor.cancel();phase(useSound?'Interpreting sound…':'Sending…'); - return ask(prompt,`${input.transcript||'Sound'} · ${(input.sound.durationMs/1000).toFixed(1)} seconds`,!useSound||localEdit(source,input.transcript)?null:musicalAdvisor.finish(input),instantPiece(input.transcript),input.transcript); + const prompt=withDrawing(musicalPrompt(input),input.drawing);const useSound=!!input.drawing||wantsSoundEvidence(input.transcript);if(!useSound)musicalAdvisor.cancel();phase(useSound?'Interpreting sound…':'Sending…'); + return ask(prompt,`${input.transcript||'Sound'} · ${(input.sound.durationMs/1000).toFixed(1)} seconds`,input.drawing||!useSound||localEdit(source,input.transcript)?null:musicalAdvisor.finish(input),input.drawing?null:instantPiece(input.transcript),input.drawing?'':input.transcript); }; window.walkiewareIsBusy=()=>busy; window.walkiewareHasReview=()=>false; diff --git a/apple/whistlegraph/Resources/Web/inference-input.mjs b/apple/whistlegraph/Resources/Web/inference-input.mjs index e1af508831..2c3e9a7791 100644 --- a/apple/whistlegraph/Resources/Web/inference-input.mjs +++ b/apple/whistlegraph/Resources/Web/inference-input.mjs @@ -1,16 +1,19 @@ -// Recording metadata belongs to history/playback, not ordinary code generation. +import {inputData,drawingEvidence} from './drawing-input.mjs'; +// Recording identity belongs to history/playback, not ordinary code generation. export function wantsSoundEvidence(transcript) { const text = transcript.trim(); return !text || /\b(?:like this|this (?:sound|melody|rhythm|tune|whistle|humming|recording)|my (?:voice|whistle|humming|singing|recording)|(?:match|follow|use|copy|visualize) (?:the |my )?(?:sound|audio|pitch|rhythm|melody))\b/i.test(text); } export function inferenceRequest(request) { - const marker = '\nINPUT DATA:\n', at = request.indexOf(marker); - if (at < 0) return request; - const input = JSON.parse(request.slice(at + marker.length)); - const transcript = input.transcript.trim(); - if (!wantsSoundEvidence(transcript)) return transcript; - const {recordingID, ...sound} = input.sound; - return (transcript || 'Create a playable interpretation of this nonverbal sound.') + - '\nSound reference: measured pitch and energy only; speech prosody is not necessarily music. Preserve the spoken intent.\n' + - JSON.stringify({words: input.words, sound}); + const input=inputData(request); + if(!input)return request; + const transcript=input.transcript.trim(); + let prompt=transcript|| (input.drawing?'Interpret this drawing.':'Create a playable interpretation of this nonverbal sound.'); + // Drawing makes gesture/sound alignment relevant even for ordinary speech. + if(input.sound&&(input.drawing||wantsSoundEvidence(transcript))){ + const {recordingID,...sound}=input.sound; + prompt+='\nSound reference: measured pitch and energy only; speech prosody is not necessarily music. Preserve the spoken intent. Loudness and pitch changes can suggest emphasis but do not establish emotion or a musical beat.\n'+JSON.stringify({words:input.words,sound}); + } + if(input.drawing)prompt+=drawingEvidence(input.drawing); + return prompt; } diff --git a/apple/whistlegraph/Resources/Web/native.js b/apple/whistlegraph/Resources/Web/native.js index 4992766df8..4028ec0eae 100644 --- a/apple/whistlegraph/Resources/Web/native.js +++ b/apple/whistlegraph/Resources/Web/native.js @@ -103,7 +103,7 @@ const blocked = () => window.walkiewareIsBusy ? window.walkiewareIsBusy() : gameMode === 'review'; $('info').querySelector('h2').textContent = 'Whistlegraph'; $('info').querySelectorAll('p')[0].textContent = 'Hold to talk. Release to make. Your words are transcribed on the iPhone; recordings stay on this phone for playback. Allow Speech and Microphone access the first time, then hold again.'; - $('info').querySelectorAll('p')[1].textContent = 'Your ww code identifies this piece. Source, versions, and live errors sync privately to your AC account so your other devices and agents can inspect and edit it. Audio stays on this phone.'; + $('info').querySelectorAll('p')[1].textContent = 'Your ww code identifies this piece. Source, versions, and live errors sync privately to your AC account so your other devices and agents can inspect and edit it. Drawings and measured sound cues travel with your requests. Recordings stay on this phone.'; $('speak').setAttribute('aria-label', 'Hold to talk to Whistlegraph'); $('export').hidden = true; $('reset').hidden = true; @@ -133,14 +133,14 @@ $('speak-label').textContent = 'Finishing…'; $('voice-heading').textContent = 'Finishing…'; send('stop'); }; - async function makeFromWords(text, sound=false) { + async function makeFromWords(text, sound=false, drawing=null) { if (completing) return; clearClock(); completing = true; voiceBusy = true; $('speak').disabled = true; $('voice-state').hidden = true; $('speak-label').textContent = 'Working…'; if (!window.walkiewareAsk) { id = ''; clearVoice(); toast('Generation is still loading. Please try again.'); return; } id = ''; - try { await (sound ? window.walkiewareAskSound(JSON.parse(text)) : window.walkiewareAsk(text)); } + try { await (sound ? window.walkiewareAskSound({...JSON.parse(text),drawing}) : window.walkiewareAskDrawing(text,drawing)); } catch { clearVoice(); toast("Could not interpret this sound. Please try again."); } } window.walkiewareRecording=()=>talking||voiceBusy||starting||completing; @@ -153,8 +153,8 @@ if (event.kind === 'partial') { $('voice-transcript').textContent = event.text; $('voice-transcript').scrollTop = $('voice-transcript').scrollHeight; requestAnimationFrame(()=>window.webkit.messageHandlers.walkie.postMessage({action:'benchmark',id:'engine',event:'transcriptPainted'})); } if (event.kind === 'musicalObservation') { try { window.walkiewareObserveSound?.(JSON.parse(event.text)); } catch {} } if (event.kind === 'sound') { $('voice-heading').textContent = 'Listening · '+event.text; } - if (event.kind === 'mixedFinal') { talking = false; makeFromWords(event.text, true); } - if (event.kind === 'final') { talking = false; makeFromWords(event.text); } + if (event.kind === 'mixedFinal') { talking = false; makeFromWords(event.text, true, event.drawing); } + if (event.kind === 'final') { talking = false; makeFromWords(event.text, false, event.drawing); } if (event.kind === 'error') { const message = event.text; id = ''; clearVoice(); toast(message); } }; $('words-form').onsubmit = event => { diff --git a/apple/whistlegraph/Sources/SpeechCapture.swift b/apple/whistlegraph/Sources/SpeechCapture.swift index 0d26abc59b..7d2236ba77 100644 --- a/apple/whistlegraph/Sources/SpeechCapture.swift +++ b/apple/whistlegraph/Sources/SpeechCapture.swift @@ -10,6 +10,7 @@ import Speech // session maps those onto published state and the JavaScript bridge. @MainActor final class SpeechCapture { /// listening · partial · sound · musicalObservation · processing · mixedFinal · error + var hasVisualInput: () -> Bool = { false } var onEvent: (_ kind: String, _ text: String, _ id: String) -> Void = { _, _, _ in } /// Latest microphone energy, for the live waveform. var onLevel: (_ rms: Double) -> Void = { _ in } @@ -181,7 +182,7 @@ import Speech musicalInput.finish { [weak self] sound in Task { @MainActor in guard let self, self.turn == id else { return } - guard !text.isEmpty || (sound["audibleMs"] as? Double ?? 0) >= 150 else { + guard !text.isEmpty || (sound["audibleMs"] as? Double ?? 0) >= 150 || self.hasVisualInput() else { self.fail("I didn’t hear words or a sound. Hold to try again."); return } let value: [String: Any] = ["transcript":text,"words":words,"sound":sound] diff --git a/apple/whistlegraph/Sources/WhistlegraphApp.swift b/apple/whistlegraph/Sources/WhistlegraphApp.swift index 5e720d1c67..7fa9d8354a 100644 --- a/apple/whistlegraph/Sources/WhistlegraphApp.swift +++ b/apple/whistlegraph/Sources/WhistlegraphApp.swift @@ -179,26 +179,30 @@ final class WhistlegraphSession: NSObject, ObservableObject, WKScriptMessageHand var narratedVersion: Int? @Published var workspaceReady = false @Published var pieces: [PieceSummary] = [] + let drawing = DrawingDraft() + private var speechStartedAt: TimeInterval? func command(_ action: String, version: Int? = nil, text: String? = nil, piece: String? = nil) { guard ["checkout", "newPiece", "openPiece", "stop", "signIn", "ask", "retry", "presentVersion", "endPresentation"].contains(action) else { return } if action == "newPiece" || action == "openPiece" { guard engineReady, !snapshot.busy, capturePhase == .idle else { return } if action == "openPiece" { guard let piece, pieces.contains(where: { $0.id == piece && !$0.current }) else { return } } + drawing.clear(); drawing.enabled = false previewSource = ""; engineReady = false } var value: [String: Any] = ["action": action] if let version { value["version"] = version } if action == "openPiece", let piece { value["piece"] = piece } if action == "ask" { - guard engineReady, !snapshot.busy, capturePhase == .idle, let text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, text.count <= 96 else { return } + guard engineReady, !snapshot.busy, capturePhase == .idle, let text, (!text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || drawing.hasInk), text.count <= 96 else { return } value["text"] = text + if let sketch = drawing.payload() { value["drawing"] = sketch } } Task { _ = try? await webView?.callAsyncJavaScript("window.walkiewareNativeCommand?.(command)", arguments: ["command": value], in: nil, contentWorld: .page) } } func beginHold() { guard engineReady, !snapshot.busy, capturePhase == .idle else { return } - captureError = nil; transcript = ""; capturePhase = .opening + captureError = nil; transcript = ""; speechStartedAt = nil; capturePhase = .opening webView?.evaluateJavaScript("voiceStart()") } func endHold() { webView?.evaluateJavaScript("voiceEnd()") } @@ -253,6 +257,7 @@ final class WhistlegraphSession: NSObject, ObservableObject, WKScriptMessageHand private let capture = SpeechCapture() override init() { super.init() + capture.hasVisualInput = { [weak self] in self?.drawing.hasInk == true } capture.onEvent = { [weak self] kind, text, id in self?.emit(kind, text: text, id: id) } capture.onLevel = { [weak self] rms in guard let self else { return }; self.microphoneLevels = Array(self.microphoneLevels.dropFirst()) + [rms] } capture.onReplayRelease = { [weak self] in self?.webView?.evaluateJavaScript("voiceEnd()", completionHandler: nil) } @@ -357,6 +362,8 @@ final class WhistlegraphSession: NSObject, ObservableObject, WKScriptMessageHand case .failure(let error): self?.emitEngine(["kind": "error", "text": error.localizedDescription]) } } + case "drawingCommitted": + if let id = body["drawingID"] as? String, let revision = body["revision"] as? Int { drawing.consume(id: id, revision: revision) } case "render": guard let source = body["source"] as? String, source.utf8.count < 500_000 else { return } if let id = body["threadID"] as? String, UUID(uuidString: id) != nil { previewThreadID = id } @@ -399,7 +406,7 @@ final class WhistlegraphSession: NSObject, ObservableObject, WKScriptMessageHand private func emit(_ kind: String, text: String = "", id: String? = nil) { switch kind { - case "listening": capturePhase = .recording; captureStarted = Date() + case "listening": capturePhase = .recording; captureStarted = Date(); speechStartedAt = ProcessInfo.processInfo.systemUptime case "partial": transcript = text case "processing", "mixedFinal", "final": capturePhase = .processing; captureStarted = nil; if kind != "processing" { ButtonSounds.play(.sent) } case "error": capturePhase = .idle; captureStarted = nil; captureError = text; ButtonSounds.play(.error) @@ -407,7 +414,8 @@ final class WhistlegraphSession: NSObject, ObservableObject, WKScriptMessageHand } if kind == "partial", !text.isEmpty { AudioBenchmark.mark("firstRecognizedWords") } if kind == "final" { AudioBenchmark.checkTranscript(text) } - let event = ["kind": kind, "text": text, "id": id ?? capture.turn] + var event: [String: Any] = ["kind": kind, "text": text, "id": id ?? capture.turn] + if ["mixedFinal", "final"].contains(kind), let sketch = drawing.payload(speechStart: speechStartedAt) { event["drawing"] = sketch } guard let data = try? JSONSerialization.data(withJSONObject: event), let json = String(data: data, encoding: .utf8) else { return } webView?.evaluateJavaScript("window.walkieNativeEvent?.(\(json))") { _, error in diff --git a/apple/whistlegraph/Sources/WhistlegraphDrawing.swift b/apple/whistlegraph/Sources/WhistlegraphDrawing.swift new file mode 100644 index 0000000000..7d56f617a3 --- /dev/null +++ b/apple/whistlegraph/Sources/WhistlegraphDrawing.swift @@ -0,0 +1,93 @@ +import SwiftUI +import UIKit + +@MainActor +final class DrawingDraft: ObservableObject { + @Published var enabled = false + @Published private(set) var strokes: [[[Double]]] = [] + @Published private(set) var revision = 0 + private(set) var id = UUID().uuidString + private var startedAt: TimeInterval = 0 + private var aspect = 4.0 / 3.0 + private var active = false + var hasInk: Bool { !strokes.isEmpty } + var full: Bool { strokes.count >= 32 || strokes.reduce(0) { $0 + $1.count } >= 1200 } + func begin(_ point: CGPoint, in size: CGSize, pressure: Double?) { + guard !full, size.width > 0, size.height > 0 else { return } + if !hasInk { startedAt = ProcessInfo.processInfo.systemUptime; aspect = size.width / size.height } + strokes.append([]); active = true + append(point, in: size, pressure: pressure, force: true) + } + func append(_ point: CGPoint, in size: CGSize, pressure: Double?, force: Bool = false) { + guard active, !strokes.isEmpty, size.width > 0, size.height > 0, + strokes.reduce(0, { $0 + $1.count }) < 1200 else { return } + let x = min(1000, max(0, point.x / size.width * 1000)).rounded() + let y = min(1000, max(0, point.y / size.height * 1000)).rounded() + let time = min(3600000, max(0, (ProcessInfo.processInfo.systemUptime - startedAt) * 1000)).rounded() + if !force, let previous = strokes.last?.last, + hypot(x - previous[0], y - previous[1]) < 3, time - previous[2] < 40 { return } + var sample = [x, y, time] + if let pressure { sample.append((min(1, max(0, pressure)) * 1000).rounded()) } + strokes[strokes.count - 1].append(sample); revision += 1 + } + func end() { active = false } + func undo() { active = false; if hasInk { strokes.removeLast(); revision += 1 } } + func clear() { active = false; strokes = []; revision += 1; id = UUID().uuidString; startedAt = 0 } + func consume(id: String, revision: Int) { if self.id == id && self.revision == revision { clear() } } + func payload(speechStart: TimeInterval? = nil) -> [String: Any]? { + guard hasInk else { return nil } + var value: [String: Any] = ["schema": "whistlegraph-drawing/v1", "id": id, "revision": revision, + "aspect": aspect, "strokes": strokes] + if let speechStart { value["speechStartMs"] = ((speechStart - startedAt) * 1000).rounded() } + return value + } +} + +struct DrawingPad: UIViewRepresentable { + @ObservedObject var draft: DrawingDraft + var interactive: Bool + func makeUIView(context: Context) -> GestureInkView { + let view = GestureInkView(); view.backgroundColor = .clear; view.isOpaque = false + view.isMultipleTouchEnabled = false; view.accessibilityIdentifier = "drawing-pad" + view.accessibilityLabel = "Drawing over the piece" + view.accessibilityHint = "Draw with a finger or Pencil while talking or typing." + return view + } + func updateUIView(_ view: GestureInkView, context: Context) { + view.draft = draft; view.isUserInteractionEnabled = interactive; view.setNeedsDisplay() + } +} + +final class GestureInkView: UIView { + var draft: DrawingDraft? + private func pressure(_ touch: UITouch) -> Double? { + guard touch.type == .pencil, touch.maximumPossibleForce > 0 else { return nil } + return Double(touch.force / touch.maximumPossibleForce) + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + draft?.begin(touch.location(in: self), in: bounds.size, pressure: pressure(touch)); setNeedsDisplay() + } + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + draft?.append(touch.location(in: self), in: bounds.size, pressure: pressure(touch)); setNeedsDisplay() + } + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + if let touch = touches.first { draft?.append(touch.location(in: self), in: bounds.size, pressure: pressure(touch), force: true) } + draft?.end(); setNeedsDisplay() + } + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { draft?.end() } + override func draw(_ rect: CGRect) { + guard let strokes = draft?.strokes else { return } + for stroke in strokes { + guard let first = stroke.first else { continue } + let path = UIBezierPath(); path.lineCapStyle = .round; path.lineJoinStyle = .round + let origin = CGPoint(x: first[0] / 1000 * bounds.width, y: first[1] / 1000 * bounds.height) + path.move(to: origin) + for p in stroke.dropFirst() { path.addLine(to: CGPoint(x: p[0] / 1000 * bounds.width, y: p[1] / 1000 * bounds.height)) } + if stroke.count == 1 { path.addLine(to: CGPoint(x: origin.x + 0.1, y: origin.y)) } + UIColor.black.withAlphaComponent(0.8).setStroke(); path.lineWidth = 7; path.stroke() + UIColor(red: 1, green: 0.14, blue: 1, alpha: 1).setStroke(); path.lineWidth = 4; path.stroke() + } + } +} diff --git a/apple/whistlegraph/Sources/WhistlegraphScreen.swift b/apple/whistlegraph/Sources/WhistlegraphScreen.swift index 35850ad2f8..8970127001 100644 --- a/apple/whistlegraph/Sources/WhistlegraphScreen.swift +++ b/apple/whistlegraph/Sources/WhistlegraphScreen.swift @@ -27,6 +27,7 @@ struct PieceRevision: Decodable, Identifiable { let utterance: String let createdAt: String let sound: PieceSound? + var hasDrawing: Bool? = nil var date: Date? { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] @@ -59,6 +60,8 @@ struct NativeLayout { struct WhistlegraphScreen: View { @ObservedObject var session: WhistlegraphSession + @ObservedObject private var drawing: DrawingDraft + init(session: WhistlegraphSession) { self.session = session; self.drawing = session.drawing } @StateObject private var narrator = VersionNarrator() @Environment(\.scenePhase) private var scenePhase @State private var held = false @@ -99,12 +102,17 @@ struct WhistlegraphScreen: View { .allowsHitTesting(session.snapshot.hasPreview && session.capturePhase == .idle) .accessibilityHidden(!session.snapshot.hasPreview) if !session.snapshot.hasPreview && !session.engineReady { ProgressView() } - if session.capturePhase == .recording || session.capturePhase == .opening { + if !drawing.enabled && (session.capturePhase == .recording || session.capturePhase == .opening) { ScrollView { Text(session.transcript.isEmpty ? (session.capturePhase == .opening ? "Opening microphone…" : "Listening…") : session.transcript) .font(.custom("ComicRelief-Regular", size: 30, relativeTo: .title)).frame(maxWidth: .infinity, alignment: .leading).padding(18) }.background(theme.background.opacity(0.95)) } + if !narrator.isPlaying && (drawing.enabled || drawing.hasInk) { + DrawingPad(draft: drawing, interactive: drawing.enabled && canTalk) + .background(drawing.enabled ? Color.black.opacity(0.16) : Color.clear) + .allowsHitTesting(drawing.enabled && canTalk) + } if let failure = session.startupFailure { VStack(spacing: 12) { Text(failure); Button("Reload") { session.reloadWorkspace() } }.padding().background(.black.opacity(0.85)) } @@ -117,6 +125,32 @@ struct WhistlegraphScreen: View { .overlay(alignment: .topTrailing) { if narrator.isPlaying { Button { narrator.stop() } label: { Image(systemName: "xmark.circle.fill").font(.largeTitle).padding(16).background(.black.opacity(0.5), in: Circle()) }.accessibilityLabel("Close version story") } } + if !narrator.isPlaying { + HStack(spacing: 18) { + Button { ButtonSounds.play(.tick); drawing.enabled.toggle() } label: { + Label("Draw", systemImage: drawing.enabled ? "pencil.tip.crop.circle.fill" : "pencil.tip.crop.circle") + }.accessibilityIdentifier("draw-control").accessibilityValue(drawing.enabled ? "On" : "Off") + .disabled(!canTalk) + if drawing.hasInk { + Button { drawing.undo() } label: { Image(systemName: "arrow.uturn.backward") } + .accessibilityLabel("Undo stroke").accessibilityIdentifier("drawing-undo").disabled(!canTalk) + Button { drawing.clear() } label: { Image(systemName: "trash") } + .accessibilityLabel("Clear drawing").accessibilityIdentifier("drawing-clear").disabled(!canTalk) + } + Spacer(minLength: 0) + if drawing.hasInk && !showComposer { + Button("Send") { ButtonSounds.play(.sent); session.command("ask", text: "") } + .accessibilityLabel("Send drawing").accessibilityIdentifier("drawing-send") + .disabled(!canTalk || session.capturePhase != .idle) + } + }.font(.title3).buttonStyle(.plain).frame(minHeight: 44) + if drawing.full { Text("Drawing full · send or undo a stroke").font(.caption) } + if drawing.enabled && (session.capturePhase == .recording || session.capturePhase == .opening) { + Text(session.transcript.isEmpty ? "Listening…" : session.transcript) + .font(.custom("ComicRelief-Regular", size: 20)).lineLimit(2) + .frame(maxWidth: .infinity, alignment: .leading).allowsHitTesting(false) + } + } if narrator.isPlaying && (session.snapshot.hasPreview || session.snapshot.hasHistory) { HStack(alignment: .bottom) { ComicTitle(text: "v\(session.snapshot.head)", size: session.layout.historySize) @@ -133,7 +167,7 @@ struct WhistlegraphScreen: View { if !narrator.error.isEmpty { Text(narrator.error).foregroundStyle(.orange) } if let failure = session.captureError { Text(failure).font(.body).foregroundStyle(.orange).frame(maxWidth: .infinity, alignment: .leading) } if !narrator.isPlaying && (session.snapshot.hasPiece || session.snapshot.hasHistory || session.snapshot.busy || session.snapshot.attempt?.status == "failed") { - VersionFeed(snapshot: session.snapshot, foreground: paper, selectionColor: paper, textSize: session.layout.historySize, disabled: session.snapshot.busy || session.capturePhase != .idle, stop: { session.command("stop") }, retry: { session.command("retry") }) { narrator.select($0, session: session) } + VersionFeed(snapshot: session.snapshot, foreground: paper, selectionColor: paper, textSize: session.layout.historySize, disabled: session.snapshot.busy || session.capturePhase != .idle, holdSelection: drawing.hasInk, stop: { session.command("stop") }, retry: { session.command("retry") }) { narrator.select($0, session: session) } } else if !narrator.isPlaying { Spacer(minLength: 0) } } .padding(.horizontal, narrator.isPlaying ? 0 : session.layout.pageInset) @@ -141,7 +175,7 @@ struct WhistlegraphScreen: View { .safeAreaInset(edge: .bottom, spacing: 8) { Group { if narrator.isPlaying { EmptyView() } else if showComposer { - InlineRequestComposer(theme: theme, disabled: session.snapshot.busy, cancel: { ButtonSounds.play(.pop); showComposer = false }) { text in + InlineRequestComposer(theme: theme, disabled: session.snapshot.busy, hasDrawing: drawing.hasInk, cancel: { ButtonSounds.play(.pop); showComposer = false }) { text in ButtonSounds.play(.sent); session.command("ask", text: text); showComposer = false } } else { @@ -174,8 +208,10 @@ struct WhistlegraphScreen: View { let progress = session.captureStarted.map { min(1, max(0, context.date.timeIntervalSince($0) / 8)) } ?? 0 VStack(spacing: 10) { if session.capturePhase == .recording { - Text("Talk").font(.custom("ComicRelief-Bold", size: 30, relativeTo: .title2)) + Text(drawing.enabled ? "Tap to send" : "Talk").font(.custom("ComicRelief-Bold", size: 30, relativeTo: .title2)) MicrophoneWaveform(levels: session.microphoneLevels).frame(height: 22) + } else if drawing.enabled { + VStack(spacing: 4) { ShoutButtonLabel(); Text("Tap to talk").font(.caption) } } else { ShoutButtonLabel() } if session.capturePhase == .recording { ProgressView(value: progress).tint(theme.buttonInk) @@ -191,10 +227,16 @@ struct WhistlegraphScreen: View { .overlay(RoundedRectangle(cornerRadius: 34, style: .continuous).strokeBorder(paper, lineWidth: 3)) .contentShape(RoundedRectangle(cornerRadius: 34, style: .continuous)) .gesture(DragGesture(minimumDistance: 0).updating($touching) { _, state, _ in state = true }.onChanged { value in + guard !drawing.enabled else { return } guard canTalk || held else { return } guard !held, session.capturePhase == .idle else { return } held = true; ButtonSounds.play(.press); session.beginHold() }.onEnded { value in + if drawing.enabled { + if session.capturePhase == .recording { ButtonSounds.play(.release); session.endHold() } + else if canTalk && session.capturePhase == .idle { ButtonSounds.play(.press); session.beginHold() } + return + } if held { ButtonSounds.play(.release); session.endHold() } held = false }) @@ -203,7 +245,7 @@ struct WhistlegraphScreen: View { } .accessibilityElement(children: .ignore) .accessibilityLabel("Talk") - .accessibilityHint("Hold to record, release to send. Up to eight seconds.") + .accessibilityHint(drawing.enabled ? "Tap to record while drawing. Tap again to send, or send automatically after eight seconds." : "Hold to record, release to send. Up to eight seconds.") .accessibilityIdentifier("talk-control") .accessibilityAddTraits(.isButton) .accessibilityAction(named: Text("Type a request")) { if canTalk { session.cancelHold(); ButtonSounds.play(.key); showComposer = true } } @@ -218,6 +260,7 @@ struct VersionFeed: View { var selectionColor: Color var textSize: CGFloat = 22 let disabled: Bool + var holdSelection = false let stop: () -> Void let retry: () -> Void let select: (Int) -> Void @@ -234,7 +277,7 @@ struct VersionFeed: View { ButtonSounds.play(.tick); withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.28)) { focusedVersion = version.id } } label: { VersionRow(version: version, foreground: foreground, selected: version.id == snapshot.head, textSize: textSize, rowHeight: rowHeight) - }.buttonStyle(.plain).disabled(disabled) + }.buttonStyle(.plain).disabled(disabled || holdSelection) .id(version.id) .visualEffect { content, geometry in let distance = max(0, geometry.frame(in: .scrollView(axis: .vertical)).minY) @@ -276,12 +319,12 @@ struct VersionFeed: View { }.frame(height: rowHeight).padding(.horizontal, 10) } } - .scrollDisabled(disabled) + .scrollDisabled(disabled || holdSelection) .onAppear { focusedVersion = snapshot.head > 0 ? snapshot.head : nil } .onChange(of: snapshot.versions.count) { _, _ in focusedVersion = snapshot.head } .task(id: focusedVersion) { try? await Task.sleep(for: .milliseconds(320)) - guard !Task.isCancelled, !disabled, let focusedVersion, focusedVersion != snapshot.head else { return } + guard !Task.isCancelled, !disabled, !holdSelection, let focusedVersion, focusedVersion != snapshot.head else { return } select(focusedVersion) } .accessibilityLabel("Version history").accessibilityIdentifier("version-rolodex") @@ -298,6 +341,7 @@ private struct VersionRow: View { var body: some View { HStack(spacing: 12) { ComicTitle(text: "v\(version.id)", size: textSize).frame(width: 48, alignment: .leading) + if version.hasDrawing == true { Image(systemName: "pencil.tip").accessibilityLabel("With drawing") } Text(version.utterance).lineLimit(1).truncationMode(.tail) .frame(maxWidth: .infinity, alignment: .trailing) } @@ -388,6 +432,7 @@ struct ComicTitle: View { struct InlineRequestComposer: View { let theme: WhistlegraphTheme var disabled: Bool + var hasDrawing = false let cancel: () -> Void let send: (String) -> Void @FocusState private var focused: Bool @@ -395,7 +440,7 @@ struct InlineRequestComposer: View { @StateObject private var keySounds = PromptKeySounds() private var prompt: String { text.trimmingCharacters(in: .whitespacesAndNewlines) } private func submit() { - guard !disabled, !prompt.isEmpty else { return } + guard !disabled, !prompt.isEmpty || hasDrawing else { return } send(prompt) } private func singleLine(_ value: String) -> String { @@ -427,7 +472,7 @@ struct InlineRequestComposer: View { Text("\(text.count) / 96").accessibilityIdentifier("request-count").monospacedDigit().foregroundStyle(.secondary) Spacer() Button("Send", action: submit) - .disabled(disabled || prompt.isEmpty) + .disabled(disabled || (prompt.isEmpty && !hasDrawing)) }.font(.title3) }.padding(14) .background(theme.surface, in: RoundedRectangle(cornerRadius: 22, style: .continuous)) diff --git a/apple/whistlegraph/Tests/drawing-input.test.mjs b/apple/whistlegraph/Tests/drawing-input.test.mjs new file mode 100644 index 0000000000..05ecd548ba --- /dev/null +++ b/apple/whistlegraph/Tests/drawing-input.test.mjs @@ -0,0 +1,38 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {normalizeDrawing,withDrawing,inputData,drawingEvidence} from '../Resources/Web/drawing-input.mjs'; +import {inferenceRequest} from '../Resources/Web/inference-input.mjs'; +import {musicalPrompt} from '../Resources/Web/musical-input.mjs'; +import {selectedBranch} from '../Resources/Web/branch-context.mjs'; +const sketch={schema:'whistlegraph-drawing/v1',id:'11111111-1111-4111-8111-111111111111',revision:3,aspect:4/3,strokes:[[[100,100,0],[200,300,200],[400,100,450]],[[400,100,1000],[450,100,1100,900]]],speechStartMs:-500}; +const sound={transcript:'make it move',words:[{text:'move',atMs:1500,durationMs:200}],sound:{schema:'walkieware-sound/v1',durationMs:2000,audibleMs:1200,frames:[{atMs:0,rms:.2,pitchHz:200},{atMs:1500,rms:.8,pitchHz:330}],onsetsMs:[1500],recordingID:'22222222-2222-4222-8222-222222222222'}}; +test('typed and drawing-only requests preserve a bounded vector attachment in one request',()=>{ + const stored=withDrawing('make this bounce',sketch),parsed=inputData(stored); + assert.equal(parsed.transcript,'make this bounce');assert.deepEqual(parsed.drawing.strokes,sketch.strokes); + assert.match(inferenceRequest(stored),/make this bounce/);assert.match(inferenceRequest(withDrawing('',sketch)),/Interpret this drawing/); + assert.doesNotMatch(inferenceRequest(stored),new RegExp(sketch.id)); + assert.equal(withDrawing('hello',null),'hello'); +}); +test('gesture and sound timing survive ordinary speech and recording identity stays local',()=>{ + const stored=withDrawing(musicalPrompt(sound),sketch),prompt=inferenceRequest(stored); + assert.match(prompt,/speechStartMs":-500/);assert.match(prompt,/"atMs":1500/);assert.match(prompt,/"rms":0.8/); + assert.doesNotMatch(prompt,/22222222|11111111|recordingID/); + assert.equal(inputData(stored).sound.recordingID,sound.sound.recordingID); + assert.doesNotMatch(inferenceRequest(musicalPrompt(sound)),/rms/,'ordinary speech without drawing still excludes sound'); +}); +test('sampling preserves stroke endpoints, pen lifts, pressure and a strict point budget',()=>{ + const points=Array.from({length:1200},(_,i)=>[i%1000,100,i*2,500]); + const result=normalizeDrawing({...sketch,strokes:[points]}); + assert.ok(result.strokes[0].length<=320);assert.deepEqual(result.strokes[0][0],points[0]);assert.deepEqual(result.strokes[0].at(-1),points.at(-1)); + assert.equal(normalizeDrawing(sketch).strokes[0][0].length,3,'finger pressure is absent'); +}); +test('malformed or oversized strokes and clocks are rejected before provider submission',()=>{ + for(const strokes of [[],Array(33).fill([[0,0,0]]),[[[1001,0,0]]],[[[0,0,0],[0,0,-1]]],[[[0,0,0,NaN]]],[[[0,0,20]],[[0,0,10]]]])assert.throws(()=>normalizeDrawing({...sketch,strokes})); + assert.throws(()=>normalizeDrawing({...sketch,speechStartMs:Infinity})); + assert.match(drawingEvidence(sketch),/ambiguous gestures/); + assert.throws(()=>withDrawing('x'.repeat(20000),sketch),/too large/); +}); +test('selected branch descriptions retain the presence of drawings without raw coordinate prose',()=>{ + const context=selectedBranch({head:1,versions:[{id:0,parent:null,source:''},{id:1,parent:0,request:withDrawing('a bird',sketch),source:''}]}); + assert.equal(context.history[0].request,'a bird [with drawing]'); +}); diff --git a/apple/whistlegraph/Tests/native-bridge.test.cjs b/apple/whistlegraph/Tests/native-bridge.test.cjs index 95f7af536c..aec7384f9e 100644 --- a/apple/whistlegraph/Tests/native-bridge.test.cjs +++ b/apple/whistlegraph/Tests/native-bridge.test.cjs @@ -75,14 +75,46 @@ const server = http.createServer(async (req, res) => { await page.goto('http://127.0.0.1:'+server.address().port+'/index.html?walkie=1'); await page.waitForFunction(()=>typeof window.walkiewareAsk==='function'); assert.deepEqual(errors,[]); + if(process.argv.includes('--drawing')) { + await page.evaluate(()=>{walkiewareEngineEvent({kind:'account',token:'fixture-only'});walkiewareEngineEvent({kind:'previewReady'});}); + const sketch={schema:'whistlegraph-drawing/v1',id:'11111111-1111-4111-8111-111111111111',revision:4,aspect:4/3,strokes:[[[100,200,0],[300,100,100],[400,300,500]]],speechStartMs:-200}; + await page.evaluate(drawing=>walkiewareNativeCommand({action:'ask',text:'make this bounce',drawing}),sketch); + await page.waitForFunction(()=>JSON.parse(localStorage.getItem('walkieware-source-versions')).head===1&&!walkiewareIsBusy()); + assert.equal(requests,1,'one combined ask'); + assert.match(JSON.stringify(inferenceBodies[0]),/DRAWING REFERENCE/); + assert.match(JSON.stringify(inferenceBodies[0]),/make this bounce/); + const saved=await page.evaluate(()=>JSON.parse(localStorage.getItem('walkieware-source-versions')).versions.at(-1)); + assert.ok(saved.request.includes('whistlegraph-drawing/v1'),'gesture belongs to committed version'); + assert.ok(await page.evaluate(()=>__nativeMessages.some(m=>m.action==='drawingCommitted'&&m.revision===4))); + // Exercise the actual microphone bridge: drawing and sound arrive in one final event. + await page.evaluate(drawing=>{ + voiceStart();const id=__nativeMessages.filter(m=>m.action==='start').at(-1).id; + walkieNativeEvent({id,kind:'listening'}); + walkieNativeEvent({id,kind:'mixedFinal',drawing,text:JSON.stringify({transcript:'follow this sweep',words:[{text:'sweep',atMs:700,durationMs:250}],sound:{schema:'walkieware-sound/v1',durationMs:1200,audibleMs:1000,frames:[{atMs:700,rms:.8,pitchHz:440}],onsetsMs:[700],recordingID:'22222222-2222-4222-8222-222222222222'}})}); + },{...sketch,revision:5}); + await page.waitForFunction(()=>JSON.parse(localStorage.getItem('walkieware-source-versions')).head===2&&!walkiewareIsBusy()); + assert.equal(requests,2);const mixed=JSON.stringify(inferenceBodies[1]); + assert.match(mixed,/speechStartMs/);assert.match(mixed,/440/);assert.match(mixed,/follow this sweep/); + assert.ok(!mixed.includes('22222222-2222'),'provider never gets recording identity'); + await page.waitForFunction(()=>__nativeMessages.some(m=>m.action==='snapshot'&&m.snapshot.versions?.some(v=>v.id===2&&v.hasDrawing))); + // A provider failure must not consume the draft or add a version. + await page.evaluate(drawing=>walkiewareAskDrawing('try another',drawing),{...sketch,revision:6}); + assert.equal(await page.evaluate(()=>JSON.parse(localStorage.getItem('walkieware-source-versions')).head),2); + assert.equal(await page.evaluate(()=>__nativeMessages.some(m=>m.action==='drawingCommitted'&&m.revision===6)),false); + await page.evaluate(drawing=>walkiewareAskDrawing('',drawing),{...sketch,strokes:[[[Infinity,0,0]]]}); + assert.equal(requests,3,'invalid drawing cannot call provider'); + assert.deepEqual(errors,[]);console.log('PASS: typed and spoken gesture input, aligned sound, per-version attachment, and failed-draft retention');return; + } if(process.argv.includes('--checked-edits')) { await page.evaluate(()=>{walkiewareEngineEvent({kind:'inferenceSettings',checkedEdits:true});walkiewareEngineEvent({kind:'account',token:'fixture-only'});walkiewareEngineEvent({kind:'previewReady'});}); const before=await page.evaluate(()=>localStorage.getItem('walkieware-source-versions')); - await page.evaluate(()=>walkiewareAsk('Make the cats bounce')); + const checkedSketch={schema:'whistlegraph-drawing/v1',id:'33333333-3333-4333-8333-333333333333',revision:1,aspect:4/3,strokes:[[[100,500,0],[200,100,500],[300,500,1000]]]}; + await page.evaluate(drawing=>walkiewareAskDrawing('Make the cats bounce',drawing),checkedSketch); await page.waitForFunction(()=>!walkiewareIsBusy()); assert.equal(requests,2,'exactly one repair generation'); assert.ok(inferenceBodies[0].messages.some(m=>JSON.stringify(m).includes('EDIT CONTRACT'))); assert.ok(inferenceBodies[1].messages.some(m=>JSON.stringify(m).includes('REPAIR THIS CANDIDATE ONCE'))); + assert.ok(inferenceBodies.every(b=>JSON.stringify(b.messages).includes('DRAWING REFERENCE')),'both passes retain gesture intent'); assert.ok(inferenceBodies.every(b=>b.max_tokens===4096&&b.thinking.type==='disabled')); const receipt=await page.evaluate(()=>JSON.parse(localStorage.getItem('walkieware-source-receipts')).at(-1).receipt); assert.equal(receipt.repairs,1);assert.equal(receipt.rounds.length,2);assert.equal(receipt.rounds[0].reportedModel,'fixture/reported'); diff --git a/apple/whistlegraph/Whistlegraph.xcodeproj/project.pbxproj b/apple/whistlegraph/Whistlegraph.xcodeproj/project.pbxproj index 548e76558c..1448d31a55 100644 --- a/apple/whistlegraph/Whistlegraph.xcodeproj/project.pbxproj +++ b/apple/whistlegraph/Whistlegraph.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ C4390906B03E8E8666BC54A3 /* SequenceBenchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55FB4AA387E716F9FEE0F83 /* SequenceBenchmark.swift */; }; D3D16A0333764D0E5C3B620D /* SpeechCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7A25F803FAC121BFECC1AD7 /* SpeechCapture.swift */; }; D9E54D2F66B9FC35483A8F45 /* PlaybackCaption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D3B156A3435E646EE0E6EEA /* PlaybackCaption.swift */; }; + FAC2911EFFCA887527D2415F /* WhistlegraphDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D9A8C801D3B9D1192D1143 /* WhistlegraphDrawing.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -64,6 +65,7 @@ 5D233DBB6F87184A91C13D41 /* MusicalInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicalInput.swift; sourceTree = ""; }; 6D137824491A01D4C19417B8 /* VersionNarrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNarrator.swift; sourceTree = ""; }; 8F9B92C3457F07429C8C2600 /* UtteranceRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtteranceRecording.swift; sourceTree = ""; }; + 99D9A8C801D3B9D1192D1143 /* WhistlegraphDrawing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhistlegraphDrawing.swift; sourceTree = ""; }; A542DD3D25782F331540474E /* Whistlegraph.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Whistlegraph.app; sourceTree = BUILT_PRODUCTS_DIR; }; A5904655414F61AE746DF6D5 /* WhistlegraphUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = WhistlegraphUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B4F9A906C6B2232BD07BEFE5 /* PromptKeySounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptKeySounds.swift; sourceTree = ""; }; @@ -120,6 +122,7 @@ 6D137824491A01D4C19417B8 /* VersionNarrator.swift */, 060870DF76C6FC078A48A276 /* WhistlegraphAccount.swift */, BA5F188938A81E704BB7F9F9 /* WhistlegraphApp.swift */, + 99D9A8C801D3B9D1192D1143 /* WhistlegraphDrawing.swift */, 1BC2C1DC5E036F689EA59561 /* WhistlegraphHeader.swift */, E66B3ADC4D6D837FABC53B8F /* WhistlegraphScreen.swift */, D25F85E540016CEBC3A6BC09 /* WhistlegraphTheme.swift */, @@ -270,6 +273,7 @@ 8A9F944BA95C971D70ADDF6B /* VersionNarrator.swift in Sources */, AFDE98CF6460438CE2205674 /* WhistlegraphAccount.swift in Sources */, 26D07334E2DEBA6705671BB0 /* WhistlegraphApp.swift in Sources */, + FAC2911EFFCA887527D2415F /* WhistlegraphDrawing.swift in Sources */, 73873BDDC483417E2F6E1143 /* WhistlegraphHeader.swift in Sources */, 4B35B066F49BDD9BD8B90B5A /* WhistlegraphScreen.swift in Sources */, 41FC24FA4CF0D6C4D0C9E7C3 /* WhistlegraphTheme.swift in Sources */, @@ -384,7 +388,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 85; + CURRENT_PROJECT_VERSION = 86; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = FB5948YR3S; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -469,7 +473,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 85; + CURRENT_PROJECT_VERSION = 86; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = FB5948YR3S; ENABLE_NS_ASSERTIONS = NO; diff --git a/apple/whistlegraph/build-icon.sh b/apple/whistlegraph/build-icon.sh new file mode 100644 index 0000000000..2c59244f43 --- /dev/null +++ b/apple/whistlegraph/build-icon.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Reuse the published Whistlegraph Dot Org artist mark, without redrawing it. +set -eu +app_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +sips -s format png -z 1024 1024 "$app_dir/../../pop/artist/whistlegraph-dot-org/wgdo-avatar-3000.jpg" --out "$app_dir/Resources/Assets.xcassets/AppIcon.appiconset/icon.png" >/dev/null diff --git a/apple/whistlegraph/icon.svg b/apple/whistlegraph/icon.svg deleted file mode 100644 index a9b56b7a64..0000000000 --- a/apple/whistlegraph/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apple/whistlegraph/project.yml b/apple/whistlegraph/project.yml index 51d34f74ee..2423340437 100644 --- a/apple/whistlegraph/project.yml +++ b/apple/whistlegraph/project.yml @@ -9,7 +9,7 @@ settings: CODE_SIGN_STYLE: Automatic SWIFT_VERSION: "5.0" MARKETING_VERSION: "0.1.0" - CURRENT_PROJECT_VERSION: "85" + CURRENT_PROJECT_VERSION: "86" targets: Whistlegraph: type: application -- 2.51.2