FreeLattice · Celeste reading home

Public, free to read, reuse, and adapt — Kirk Patrick Miller's notice for this edition. Not a patent grant.

FLINT_NEXT_TREE_ANY_MODEL_v0.1.txt · c03

0d55c697ae · words 1595 · characters 23082–35978

Previous section · Next section

Exact section text · Full source · Source index

Archive pages whose extracted text matched this section: 1407, 1408, 1409. Unmatched pages stay on the source record with the checked page method. They are not guessed into a section.

No topic tag was earned for this page under the recorded evidence rule.

  // v-tree-any-model-v0: Connect and paste add to the sky; they never wipe it.
  // found = { name, url, models: [names], model }
  function mergeFound(found) {
    if (!found || !found.url) return null;
    var url = String(found.url);
    var models = (found.models || []).map(function (m) {
      return String((m && (m.name || m.model || m.id)) || m);
    }).filter(Boolean).slice(0, MODEL_CAP);
    var model = String(found.model || '');
    if (model && !modelOnList(models, model)) models.unshift(model);
    var entry = getRemembered() || { name: '', url: '', model: '', models: [], minds: [],
      roster: [], gatheringBinds: {}, speakingChair: '' };
    normalizeEntry(entry);
    var minds = mindsFromEntry(entry);
    var target = null;
    var i;
    for (i = 0; i < minds.length; i++) {
      if (String(minds[i].url || '') === url) { target = minds[i]; break; }
    }
    if (!target) {
      target = { name: String(found.name || 'a mind at home'), url: url, models: [], model: '', primary: false };
      minds.push(target);
    }
    if (found.name) target.name = String(found.name);
    target.models = models.length ? models : (target.models || []);
    target.model = model || target.model || '';
    entry.minds = minds.slice(0, 7);
    syncEntryFromMind(entry, target);   // sets primary, name, url, models, model; clears modelNote
    entry.foundAt = new Date().toISOString();
    remember(entry);                    // roster, gatheringBinds, speakingChair untouched
    return entry;
  }

  Export it on window.LocalMindProbe (904-936) as mergeFound: mergeFound, plus MODEL_CAP: MODEL_CAP.

6.2 docs/modules/fl-connect.js (SHARED CORE, both repos, byte-identical). Inside function remember,
  Alpha branch (text anchor "if (isAlpha()) {"), BEFORE "var entry = {" add:

      // v-tree-any-model-v0: on the Tree, Connect merges into the sky (all models, other doors kept)
      if (root.LocalMindProbe && typeof root.LocalMindProbe.mergeFound === 'function') {
        try {
          var doorName = doorNameFor(base, mind.port); // 11434 -> 'Ollama', any other 127.0.0.1 port -> 'Bridge'
          var merged = root.LocalMindProbe.mergeFound({
            name: doorName,
            url: base ? String(base).replace(/\/+$/, '') + '/api/tags' : '',
            models: mind.models || [],
            model: name
          });
          if (merged) { stopLoop(); return merged; }
        } catch (eM) {}
      }

  Keep the old "var entry = {...}" path below it untouched (layer): it still runs on an older
  local-mind-probe.js without mergeFound, and in tests that stub LocalMindProbe.
  Helper (place above remember, under the marker):
      function doorNameFor(base, port) {
        var p = String(port || '');
        var m = String(base || '').match(/:(\d{1,5})\/?$/);
        if (!p && m) p = m[1];
        return (!p || p === '11434') ? 'Ollama' : 'Bridge';
      }

6.3 fl-connect.js fillModelLists (main 456 / #83 473): pass the list along:
      remember({ name: name, base: report.base, port: report.port, models: report.models });
  FreeLattice's branch of remember() ignores mind.models, so FreeLattice behavior is unchanged.

6.4 fl-connect.js chosen mark (T9), both repos. In fillModelLists, after btn.dataset.model = name;:
      // v-tree-any-model-v0: show what speaks
      var isOn = chosenModelName() === name;
      btn.setAttribute('aria-pressed', isOn ? 'true' : 'false');
      if (isOn) btn.classList.add('is-chosen');
  and a helper near hasLocalMind:
      function chosenModelName() {
        try {
          if (isAlpha()) {
            var m = root.LocalMindProbe && root.LocalMindProbe.getRemembered && root.LocalMindProbe.getRemembered();
            return m && m.model ? String(m.model) : '';
          }
          if (root.state && root.state.ollamaModel) return String(root.state.ollamaModel);
          return String(localStorage.getItem('fl_ollamaModel') || '');
        } catch (e) { return ''; }
      }
  In ensureStyles() add under the marker: .flc-model.is-chosen { outline: 2px solid currentColor; font-weight: 600; }
  (keep contrast; no new colors needed). refreshUI({force:true}) after a tap already repaints.
  Note: render() skips repaint when the signature is unchanged; the tap path forces it. Confirm the
  mark moves on a second tap in both repos.

6.5 Tree-only paste path (tryAddress onFound, local-mind-probe.js 877-883) stays on onFound, which
  already merges through entryFromFoundList for a fresh Find. For a pasted door when a sky already
  exists, prefer mergeFound({ name:'a mind at home', url: result.url, models: result.models }) so the
  paste adds a door instead of replacing the list. Keep onFound as the fallback when nothing is remembered.

======================================================================
7. STEP 4: see every door, 127.0.0.1 only (T7, T11)
======================================================================

7.1 look() Bridge-first (240-274): when bridged, still ask the non-Ollama DOORS (lmstudio, llamacpp,
  jan, gpt4all, koboldcpp; skip id 'ollama' because the Bridge already speaks for it) and return
  foundList = [bridge].concat(other found doors). Same 2.5 s timeouts, same single tap. Keep via:'fl-connect'.
  Code shape:
      // v-tree-any-model-v0: the Bridge speaks for Ollama; other doors still get asked
      return bridgeFirst.then(function (bridged) {
        if (!bridged) { ...existing DOORS path unchanged... }
        var others = DOORS.filter(function (d) { return d.id !== 'ollama'; });
        return Promise.all(others.map(function (d) {
          return fetchDoor(d.url, 2500).then(function (r) { r.id = d.id; r.name = d.name; return r; });
        })).then(function (results) {
          results.forEach(function (r) { if (r.ok) bridged.foundList.push(r); });
          bridged.tried = 1 + results.length;
          bridged.results = results;
          return bridged;
        });
      });

7.2 tryAddress (306-336): loopback only. At the top, after the empty check, layer:
      // v-tree-any-model-v0: 127.0.0.1 only. Nothing leaves this computer.
      var hostPart = text.replace(/^https?:\/\//i, '').split(/[\/?#]/)[0];
      var hostOnly = hostPart.replace(/:\d{1,5}$/, '').toLowerCase();
      if (hostOnly === 'localhost') text = text.replace(/localhost/i, '127.0.0.1');
      if (!(hostOnly === '127.0.0.1' || hostOnly === 'localhost' || hostOnly === '[::1]')) {
        return Promise.resolve({ ok: false, refused: true, pasted: String(raw || '') });
      }
  and in the Try button handler (867-891), before the other branches:
      if (result.refused) {
        setStatus(root, 'Only this computer\'s own doors (127.0.0.1). Nothing was sent.', 'warn');
        return;
      }
  Zero fetches for a refused address (test it with a fetch spy).
  Also accept a bare port ("1234" or ":1234") as http://127.0.0.1:1234, mirroring FlConnect's normalizePort.

7.3 One honesty line (static text, textContent) under the existing honesty paragraph (824-827):
      'LM Studio: turn on CORS in its server settings so this garden can hear it.'

======================================================================
8. STEP 5: say what speaks, and change it from where you are (T8, T12)
======================================================================

8.1 garden-thread.js heart (505). Layer:
      // v-tree-any-model-v0: name the model, not only the door
      var speaking = mind.model || (mind.models && mind.models[0]) || '';
      heart.textContent = 'Listening: ' + (mind.name || 'a mind at home') +
        (speaking && speaking !== mind.name ? ' \u00b7 ' + speaking : '') + '. On this machine only.';
  Keep the old line as a comment. (\u00b7 is the middle dot already used in the Gathering copy.)
  Add one quiet button next to the heart, only when a mind is remembered and the thread is NOT already
  inside Settings: text 'change mind', data-thread-change="1"; on click:
      if (window.GardenRooms && typeof GardenRooms.openPlace === 'function') GardenRooms.openPlace('settings');
      else location.href = 'settings.html';
  (openPlace(id) is already exported: garden-rooms.js "window.GardenRooms.openPlace = openPlace;"
  (line 1730 on 22b913e, 1751 on the #83 tree). After #83 it restores the galaxy first. No new export needed.)
  The thread must repaint its heart on 'fl-alpha-mind-remembered' wherever it is mounted (room chat
  already re-mounts at garden-rooms.js 1567; check the overlay thread and add a listener if missing).

8.2 garden-trainer.js (T12). Add a helper near the top of the module scope:
      // v-tree-any-model-v0: the Tree remembers its model in fl_alpha_local_mind
      function activeModelName() {
        try {
          var a = localStorage.getItem('fl_active_model');
          if (a) return a;
          var m = window.LocalMindProbe && LocalMindProbe.getRemembered && LocalMindProbe.getRemembered();
          if (m && m.model) return String(m.model);
        } catch (e) {}
        return '';
      }
  and replace each localStorage.getItem('fl_active_model') at 731, 768, 899, 901, 1092 with
  activeModelName(). Leave the 'llama3.2' and 'phi-pathway' fallbacks as the last resort (layer).

======================================================================
9. OUT OF SCOPE FOR v0.1 (write these into the doc as "later", do not build)
======================================================================
  - Round Table: sitters are doors (round-table-sitting.js 86-100). v0.2 idea: sitters from roster
    seats and Gathering binds, so two models at one Ollama door can sit together.
  - LM Studio through the Ollama Bridge: bridge/proxy-core.js proxies only 11434. A FreeLattice-side
    Bridge paste could add a second upstream (127.0.0.1:1234 /v1/*). Not here.
  - Agent Bridge 3141: stays out of the Tree's model path (pairing gate, #120 lock, Ollama-only).
  - Teaching FlConnect.probe the OpenAI /v1/models shape: would change FreeLattice behavior. Not here.

======================================================================
10. TESTS
======================================================================

10.1 New Alpha test: docs/modules/tree-any-model.test.js (node, same vm/localStorage stub style as
  local-mind-probe.test.js; no network, stubbed fetch that records URLs). Assertions:
   1  parseModelNames of 7 Ollama names returns 7; of 70 returns 64; duplicates collapse.
   2  paintConstellation with 12 models: 8 radios + chosen if beyond + a [data-sky-more] button
      reading 'Show all 12 models'; after clicking it, 12 radios. All radios role=radio.
   3  chooseModel on the 7th model succeeds after a 7-model Find.
   4  getChairSeatOptions returns all models of all doors (7 + 2 = 9 in the fixture), not 12-capped
      and not 5-capped.
   5  mergeFound keeps other doors: Find (Ollama 7 + LM Studio 2), then mergeFound({url: 11434/api/tags,
      models: 7 names, model: 'gemma2:9b'}) -> minds.length 2, models.length 7, model gemma2:9b,
      name 'Ollama', roster and gatheringBinds unchanged, speakingChair unchanged.
   6  Any order: Connect(gemma2) -> Find -> model still gemma2:9b, no modelNote containing 'no longer'.
   7  Door move: prior model qwen2.5:14b at http://127.0.0.1:11435/api/tags; Find returns it only at
      http://127.0.0.1:11434/api/tags -> model kept, primary 11434, modelNote starts 'Still speaking with'.
   8  Truly gone: prior 'ghost:1b' absent everywhere -> today's speakModelGone line (kept).
   9  tryAddress('192.168.1.77:11434'), ('example.com'), ('10.0.0.2:1234') -> refused, fetch spy saw 0 calls.
      tryAddress('localhost:1234') fetches http://127.0.0.1:1234/... only. tryAddress('1234') -> 127.0.0.1:1234.
  10  look() with a helped Bridge stub AND an LM Studio fetch stub -> foundList has both (Bridge first).
  11  fl-connect.js: remember({name, base:'http://127.0.0.1:11434', models:[7]}) on the Tree (isAlpha true,
      LocalMindProbe stub with mergeFound) calls mergeFound with all 7 names; without mergeFound the old
      replace path still runs (layer proof).
  12  fl-connect.js source contains 'v-tree-any-model-v0', 'aria-pressed', 'is-chosen', 'chosenModelName'.
  13  garden-thread.js source: heart names the model (regex on 'Listening: ' ... speaking) and has
      data-thread-change; garden-trainer.js has function activeModelName and no bare
      localStorage.getItem('fl_active_model') outside it.
  14  No confirm( in any file you touched; no '\u2014' (em dash) in any line you added; no
      'Access-Control-Allow-Origin', '*' pairs; no innerHTML assignment containing a model variable.
  15  Print exactly: SMOKE_OK tree any model v0.1
  Existing local-mind-probe.test.js stays green untouched (its fixture has 5 models, so "stars.length 5"
  still holds). If any existing test pins the 5-cap or the 12-cap, layer an updated assertion; do not
  delete the old test file.