@@ -9,6 +9,29 @@ type AttentionDirection = 'increment' | 'decrement';
99type AdjustmentResult = { prompt : string ; selectionStart : number ; selectionEnd : number } ;
1010
1111const ATTENTION_STEP = 1.1 ;
12+ const NUMERIC_ATTENTION_STEP = 0.1 ;
13+
14+ /**
15+ * Check if a weight is approximately ATTENTION_STEP^n for some integer n.
16+ * Returns n if so, or null if the weight is not a power of ATTENTION_STEP.
17+ */
18+ function getAttentionStepCount ( weight : number ) : number | null {
19+ if ( weight <= 0 ) {
20+ return null ;
21+ }
22+ if ( Math . abs ( weight - 1.0 ) < 0.001 ) {
23+ return 0 ;
24+ }
25+ const n = Math . round ( Math . log ( weight ) / Math . log ( ATTENTION_STEP ) ) ;
26+ if ( n === 0 ) {
27+ return null ;
28+ }
29+ const expected = Math . pow ( ATTENTION_STEP , n ) ;
30+ if ( Math . abs ( expected - weight ) < 0.005 ) {
31+ return n ;
32+ }
33+ return null ;
34+ }
1235
1336/**
1437 * Adjusts the attention of the prompt at the current cursor/selection position.
@@ -68,10 +91,20 @@ export function adjustPromptAttention(
6891 }
6992
7093 for ( const terminal of selectedTerminals ) {
71- if ( direction === 'increment' ) {
72- terminal . weight *= ATTENTION_STEP ;
94+ if ( terminal . hasNumericAttention ) {
95+ // Additive step for explicit numeric weights (e.g. 1.15 → 1.25 / 1.05)
96+ if ( direction === 'increment' ) {
97+ terminal . weight = Number ( ( terminal . weight + NUMERIC_ATTENTION_STEP ) . toFixed ( 4 ) ) ;
98+ } else {
99+ terminal . weight = Number ( ( terminal . weight - NUMERIC_ATTENTION_STEP ) . toFixed ( 4 ) ) ;
100+ }
73101 } else {
74- terminal . weight /= ATTENTION_STEP ;
102+ // Multiplicative step for +/- syntax weights
103+ if ( direction === 'increment' ) {
104+ terminal . weight *= ATTENTION_STEP ;
105+ } else {
106+ terminal . weight /= ATTENTION_STEP ;
107+ }
75108 }
76109 }
77110
@@ -97,28 +130,37 @@ type Terminal = {
97130 weight : number ;
98131 range : { start : number ; end : number } ;
99132 hasExplicitAttention : boolean ;
133+ hasNumericAttention : boolean ;
100134 parentRange ?: { start : number ; end : number } ;
101135 isSelected : boolean ;
102136} ;
103137
104- function flattenAST ( ast : ASTNode [ ] , currentWeight = 1.0 , parentRange ?: { start : number ; end : number } ) : Terminal [ ] {
138+ function flattenAST (
139+ ast : ASTNode [ ] ,
140+ currentWeight = 1.0 ,
141+ parentRange ?: { start : number ; end : number } ,
142+ numericAttention = false
143+ ) : Terminal [ ] {
105144 let terminals : Terminal [ ] = [ ] ;
106145
107146 for ( const node of ast ) {
108147 let nodeWeight = currentWeight ;
148+ let nodeNumericAttention = numericAttention ;
109149 if ( 'attention' in node && node . attention ) {
110150 nodeWeight *= parseAttention ( node . attention ) ;
151+ nodeNumericAttention = typeof node . attention === 'number' ;
111152 }
112153
113154 if ( node . type === 'group' ) {
114- terminals . push ( ...flattenAST ( node . children , nodeWeight , node . range ) ) ;
155+ terminals . push ( ...flattenAST ( node . children , nodeWeight , node . range , nodeNumericAttention ) ) ;
115156 } else {
116157 terminals . push ( {
117158 text : node . type === 'word' ? node . text : node . value ,
118159 type : node . type ,
119160 weight : nodeWeight ,
120161 range : node . range ,
121162 hasExplicitAttention : 'attention' in node && ! ! node . attention ,
163+ hasNumericAttention : nodeNumericAttention ,
122164 parentRange : parentRange ,
123165 isSelected : false ,
124166 } ) ;
@@ -234,9 +276,14 @@ function groupTerminals(terminals: Terminal[]): ASTNode[] {
234276 return j ;
235277 } ;
236278
237- // Check for + (>= 1.1)
238- if ( weight >= ATTENTION_STEP - 0.001 ) {
239- const j = findRunEnd ( ( w ) => w >= ATTENTION_STEP - 0.001 ) ;
279+ const stepCount = getAttentionStepCount ( weight ) ;
280+
281+ // Check for + (positive power of ATTENTION_STEP)
282+ if ( stepCount !== null && stepCount > 0 ) {
283+ const j = findRunEnd ( ( w ) => {
284+ const sc = getAttentionStepCount ( w ) ;
285+ return sc !== null && sc > 0 ;
286+ } ) ;
240287
241288 let runStart = i ;
242289 let runEnd = j ;
@@ -277,9 +324,12 @@ function groupTerminals(terminals: Terminal[]): ASTNode[] {
277324 continue ;
278325 }
279326
280- // Check for - (<= 0.909)
281- if ( weight <= 1 / ATTENTION_STEP + 0.001 ) {
282- const j = findRunEnd ( ( w ) => w <= 1 / ATTENTION_STEP + 0.001 ) ;
327+ // Check for - (negative power of ATTENTION_STEP)
328+ if ( stepCount !== null && stepCount < 0 ) {
329+ const j = findRunEnd ( ( w ) => {
330+ const sc = getAttentionStepCount ( w ) ;
331+ return sc !== null && sc < 0 ;
332+ } ) ;
283333
284334 let runStart = i ;
285335 let runEnd = j ;
@@ -336,16 +386,8 @@ function groupTerminals(terminals: Terminal[]): ASTNode[] {
336386
337387 const weightStr = Number ( weight . toFixed ( 4 ) ) ;
338388
339- if ( children . length === 1 ) {
340- const child = children [ 0 ] ! ;
341- if ( child . type === 'word' || child . type === 'group' ) {
342- nodes . push ( { ...child , attention : weightStr } ) ;
343- } else {
344- nodes . push ( { type : 'group' , children, attention : weightStr , range : { start : 0 , end : 0 } , isSelection } ) ;
345- }
346- } else {
347- nodes . push ( { type : 'group' , children, attention : weightStr , range : { start : 0 , end : 0 } , isSelection } ) ;
348- }
389+ // Always create a group for numeric weights to preserve parentheses in output
390+ nodes . push ( { type : 'group' , children, attention : weightStr , range : { start : 0 , end : 0 } , isSelection } ) ;
349391 i = j ;
350392 }
351393 }
@@ -377,10 +419,10 @@ function addAttention(current: Attention | undefined, added: string): Attention
377419 }
378420 if ( typeof current === 'number' ) {
379421 if ( added === '+' ) {
380- return current * ATTENTION_STEP ;
422+ return Number ( ( current * ATTENTION_STEP ) . toFixed ( 4 ) ) ;
381423 }
382424 if ( added === '-' ) {
383- return current / ATTENTION_STEP ;
425+ return Number ( ( current / ATTENTION_STEP ) . toFixed ( 4 ) ) ;
384426 }
385427 return current ;
386428 }
0 commit comments