mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Merge branch 'claude/discussion-file-linking-nzgd0d'
This commit is contained in:
8 files changed
+152
-63
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.8 KiB |
@@ -2,12 +2,15 @@ package loki
|
|||||||
|
|
||||||
// sys_brand_icon.go — l'icône de la marque Loki, rendue à la volée.
|
// sys_brand_icon.go — l'icône de la marque Loki, rendue à la volée.
|
||||||
//
|
//
|
||||||
// RÉPLIQUE EXACTE du favicon de l'UI web : carré à coins arrondis NOIR + « j »
|
// RÉPLIQUE EXACTE du favicon de l'UI web : carré à coins arrondis SAUGE + « j »
|
||||||
// blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12). Une seule source pour
|
// blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12). Une seule source pour
|
||||||
// tous les usages — zone de notification Windows, barre de menus macOS, icône du
|
// tous les usages — zone de notification Windows, barre de menus macOS, icône du
|
||||||
// .exe — pour qu'ils ne puissent plus diverger comme quand le favicon est passé
|
// .exe — pour qu'ils ne puissent plus diverger comme quand le favicon est passé
|
||||||
// au noir en laissant les icônes système en bleu.
|
// au noir en laissant les icônes système en bleu.
|
||||||
//
|
//
|
||||||
|
// La sauge est --accent de l'interface (#5E7F5A) : l'icône de l'onglet et celle
|
||||||
|
// du bureau portent la couleur de l'app, pas un carré noir anonyme.
|
||||||
|
//
|
||||||
// Aucun asset binaire à committer : tout est dessiné ici, et l'icône du .exe est
|
// Aucun asset binaire à committer : tout est dessiné ici, et l'icône du .exe est
|
||||||
// produite par `go generate ./cmd/loki` (voir tools/gen-icon).
|
// produite par `go generate ./cmd/loki` (voir tools/gen-icon).
|
||||||
|
|
||||||
@@ -22,6 +25,8 @@ import (
|
|||||||
const trayIconSize = 32
|
const trayIconSize = 32
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// brandSage = --accent du thème clair. Le blanc du « j » s'y lit à 4,9:1.
|
||||||
|
brandSage = color.RGBA{0x5e, 0x7f, 0x5a, 0xff}
|
||||||
brandBlack = color.RGBA{0x00, 0x00, 0x00, 0xff}
|
brandBlack = color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||||
brandWhite = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
brandWhite = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
brandClear = color.RGBA{0, 0, 0, 0}
|
brandClear = color.RGBA{0, 0, 0, 0}
|
||||||
@@ -80,9 +85,9 @@ func encodePNG(img *image.RGBA) []byte {
|
|||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BrandIconPNG rend l'icône de marque (noir + « j » blanc) en PNG de n pixels.
|
// BrandIconPNG rend l'icône de marque (sauge + « j » blanc) en PNG de n pixels.
|
||||||
// Exporté pour le générateur d'icône du .exe (tools/gen-icon).
|
// Exporté pour le générateur d'icône du .exe (tools/gen-icon).
|
||||||
func BrandIconPNG(n int) []byte { return encodePNG(brandIconImage(n, brandBlack, brandWhite)) }
|
func BrandIconPNG(n int) []byte { return encodePNG(brandIconImage(n, brandSage, brandWhite)) }
|
||||||
|
|
||||||
// brandTemplatePNG rend la variante « template » attendue par macOS : seule la
|
// brandTemplatePNG rend la variante « template » attendue par macOS : seule la
|
||||||
// couche alpha compte, le système colore la forme selon le thème de la barre de
|
// couche alpha compte, le système colore la forme selon le thème de la barre de
|
||||||
|
|||||||
+72
-30
@@ -12,8 +12,8 @@
|
|||||||
<!-- Favicon (même marque « loki » que la landing/portail) + icône PWA (écran d'accueil
|
<!-- Favicon (même marque « loki » que la landing/portail) + icône PWA (écran d'accueil
|
||||||
iOS). SVG inline pour le favicon, PNG 180×180 en data-URI pour apple-touch-icon
|
iOS). SVG inline pour le favicon, PNG 180×180 en data-URI pour apple-touch-icon
|
||||||
(iOS n'accepte pas le SVG pour l'icône d'accueil). Aucun fichier externe requis. -->
|
(iOS n'accepte pas le SVG pour l'icône d'accueil). Aucun fichier externe requis. -->
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><rect width='12' height='12' rx='2' ry='2' fill='%23000'/><rect x='6' y='3' width='2' height='2' fill='%23fff'/><rect x='6' y='5' width='2' height='2' fill='%23fff'/><rect x='4' y='7' width='2' height='2' fill='%23fff'/></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><rect width='12' height='12' rx='2' ry='2' fill='%235E7F5A'/><rect x='6' y='3' width='2' height='2' fill='%23fff'/><rect x='6' y='5' width='2' height='2' fill='%23fff'/><rect x='4' y='7' width='2' height='2' fill='%23fff'/></svg>">
|
||||||
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAABBUlEQVR42u3SAQkAAAwCQfuX3lI4GNxF0E8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIArU2NbcYhDHOIQhzgQB+JAHIgDcSAOxIE4xCEOcYhDHIgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQByIQxziEIc4xIE4EAfiQByIA3EgDsQhDnGIQxziEIc4EAfiQByIAxciDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAciEMc4hCHOMQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfLBHMhi4TnG6EAAAAAElFTkSuQmCC">
|
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAADD0lEQVR4nOzdW27aWgBGYag6ylZqh1I6lFZKppmjEylRLlyMsbG9+n3vsP2w8mfDC193d/Dj17ene5zDuv39/bif+4xZDhAwQ8wR+GRvKGJuMVXcN7+JkJnSrWGPfrGQmdPYsL+MeZGYmdvYxq76KxAyS7hmrQcvtJhZyjXtDQpazCxtaIMXgxYzazGkxbNBi5m1udTkyaDFzFqda3PU13awVkeDts6s3alGPwUtZrbiWKuuHKS8C9o6szUfm7XQpLwGbZ3ZqrftWmhSBE3Kc9CuG2zdS8MWmhRBkyJoUvbuz5RYaFIETYqgSRE0KYImRdCkCJoUQZMiaFIETYqgSRE0KYImRdCkCJoUQZMiaFIETYqgSbnLj9ez2/05PCxy7s/D90XOXYqFJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAialK9LP8C9/Tk8LHLuz8P3Rc7911hoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZl/+PXt6elHwKmYqFJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgibly9/fj/ulHwKm8H/LFpoUQZMiaFKeg3aPZuteGrbQpAialNegXTvYqrftWmhS3gVtpdmaj81aaFI+BW2l2YpjrR5daFGzdqcadeUg5WTQVpq1Otfm2YUWNWtzqcmLVw5RsxZDWhx0hxY1Sxva4OAPhaJmKde0NypSPwXHPYwZ0VFf21lr5ja2sZvDtNZM6daxnGxphc0tpvqvP8vVQdwMMcfV9S53YYGzu9Nnr/8CAAD//xZRk2OJYiOqAAAAAElFTkSuQmCC">
|
||||||
<!-- Marque le mode PWA AVANT le rendu : la hauteur de coque et le défilement du
|
<!-- Marque le mode PWA AVANT le rendu : la hauteur de coque et le défilement du
|
||||||
document en dépendent (voir styles.css). ⚠️ On NE PEUT PAS se fier au média
|
document en dépendent (voir styles.css). ⚠️ On NE PEUT PAS se fier au média
|
||||||
`display-mode:standalone` : WebKit l'adosse au `display` du MANIFESTE, or cette
|
`display-mode:standalone` : WebKit l'adosse au `display` du MANIFESTE, or cette
|
||||||
@@ -66,7 +66,8 @@ document.documentElement.setAttribute('data-perf',localStorage.getItem('loki-per
|
|||||||
--ok:#5e7f5a;--warn:#a86a2a;--err:#c0503f;--mag:#a86a2a;
|
--ok:#5e7f5a;--warn:#a86a2a;--err:#c0503f;--mag:#a86a2a;
|
||||||
--code-bg:#f4f6f5;--danger-bg:#faeae7;--warn-bg:#f7f0e5;
|
--code-bg:#f4f6f5;--danger-bg:#faeae7;--warn-bg:#f7f0e5;
|
||||||
--add:#5e7f5a;--add-bg:rgba(94,127,90,.11);--del:#c0503f;--del-bg:rgba(192,80,63,.12);
|
--add:#5e7f5a;--add-bg:rgba(94,127,90,.11);--del:#c0503f;--del-bg:rgba(192,80,63,.12);
|
||||||
--shadow:0 1px 2px rgba(15,23,42,.05);--shadow-lift:0 4px 16px rgba(15,23,42,.08)}
|
--shadow:0 1px 2px rgba(15,23,42,.05);--shadow-lift:0 4px 16px rgba(15,23,42,.08);
|
||||||
|
--shadow-card:0 2px 10px rgba(15,23,42,.07),0 1px 3px rgba(15,23,42,.05)}
|
||||||
/* « Deep Dark » : fond #0F172A, cartes #1E293B. */
|
/* « Deep Dark » : fond #0F172A, cartes #1E293B. */
|
||||||
[data-theme="dark"]{color-scheme:dark;
|
[data-theme="dark"]{color-scheme:dark;
|
||||||
--bg:#0f172a;--panel:#1e293b;--bubble:#1e293b;--border:rgba(148,163,184,.17);
|
--bg:#0f172a;--panel:#1e293b;--bubble:#1e293b;--border:rgba(148,163,184,.17);
|
||||||
@@ -77,7 +78,8 @@ document.documentElement.setAttribute('data-perf',localStorage.getItem('loki-per
|
|||||||
--ok:#8fb08c;--warn:#d9a55f;--err:#e0705c;--mag:#d9a55f;
|
--ok:#8fb08c;--warn:#d9a55f;--err:#e0705c;--mag:#d9a55f;
|
||||||
--code-bg:#131c2f;--danger-bg:#3a1f1c;--warn-bg:#33291b;
|
--code-bg:#131c2f;--danger-bg:#3a1f1c;--warn-bg:#33291b;
|
||||||
--add:#8fb08c;--add-bg:rgba(143,176,140,.16);--del:#e0705c;--del-bg:rgba(224,112,92,.16);
|
--add:#8fb08c;--add-bg:rgba(143,176,140,.16);--del:#e0705c;--del-bg:rgba(224,112,92,.16);
|
||||||
--shadow:none;--shadow-lift:0 4px 16px rgba(0,0,0,.35)}
|
--shadow:none;--shadow-lift:0 4px 16px rgba(0,0,0,.35);
|
||||||
|
--shadow-card:0 2px 12px rgba(0,0,0,.30),0 1px 3px rgba(0,0,0,.22)}
|
||||||
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
|
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
|
||||||
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
|
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
|
||||||
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
|
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
|
||||||
@@ -1601,12 +1603,12 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
|
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
|
||||||
#chat{padding-top:22px}
|
#chat{padding-top:22px}
|
||||||
#chat .msg.assistant{background:var(--bubble);border:1px solid var(--border);
|
#chat .msg.assistant{background:var(--bubble);border:1px solid var(--border);
|
||||||
border-radius:16px;padding:14px 18px;max-width:100%;box-shadow:var(--shadow)}
|
border-radius:16px;padding:14px 18px;max-width:100%;box-shadow:var(--shadow-card)}
|
||||||
#chat .msg.assistant.errmsg{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 45%,transparent)}
|
#chat .msg.assistant.errmsg{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 45%,transparent)}
|
||||||
/* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
|
/* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
|
||||||
contraste dans les DEUX variantes. */
|
contraste dans les DEUX variantes. */
|
||||||
#chat .msg.user{background:var(--accent);color:var(--on-accent);border-radius:18px 18px 6px 18px;
|
#chat .msg.user{background:var(--accent);color:var(--on-accent);border-radius:18px 18px 6px 18px;
|
||||||
padding:11px 16px;max-width:75%}
|
padding:11px 16px;max-width:75%;box-shadow:var(--shadow-card)}
|
||||||
#chat .msg.user .label{color:var(--on-accent);opacity:.75}
|
#chat .msg.user .label{color:var(--on-accent);opacity:.75}
|
||||||
#chat .msg.user .body a{color:var(--on-accent);text-decoration:underline}
|
#chat .msg.user .body a{color:var(--on-accent);text-decoration:underline}
|
||||||
#chat .msg.user code{background:rgba(255,255,255,.18);color:inherit}
|
#chat .msg.user code{background:rgba(255,255,255,.18);color:inherit}
|
||||||
@@ -1623,13 +1625,16 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
#send,#stop{width:34px;height:34px;min-height:34px;padding:0;border-radius:999px;
|
#send,#stop{width:34px;height:34px;min-height:34px;padding:0;border-radius:999px;
|
||||||
display:inline-flex;align-items:center;justify-content:center;flex:none}
|
display:inline-flex;align-items:center;justify-content:center;flex:none}
|
||||||
#send svg,#stop svg{display:block}
|
#send svg,#stop svg{display:block}
|
||||||
#send:hover:not(:disabled){opacity:1;transform:none;background:color-mix(in srgb,var(--accent) 85%,var(--text))}
|
#send{background:transparent;border-color:transparent;color:var(--accent)}
|
||||||
|
#send:hover:not(:disabled){opacity:1;transform:none;background:var(--accent-bg);color:var(--accent)}
|
||||||
#send:active:not(:disabled){transform:scale(.92)}
|
#send:active:not(:disabled){transform:scale(.92)}
|
||||||
#stop:hover{opacity:1;background:color-mix(in srgb,var(--err) 85%,var(--text))}
|
#send:disabled{background:transparent;color:var(--dim)}
|
||||||
|
#stop{background:transparent;border-color:transparent;color:var(--err)}
|
||||||
|
#stop:hover{opacity:1;background:var(--danger-bg);color:var(--err)}
|
||||||
/* Les outils de la saisie (joindre, fichiers, envoyer) montent DANS la pilule,
|
/* Les outils de la saisie (joindre, fichiers, envoyer) montent DANS la pilule,
|
||||||
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient
|
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient
|
||||||
ce qu'il doit être — une jauge de contexte, rien d'autre. */
|
ce qu'il doit être — une jauge de contexte, rien d'autre. */
|
||||||
#inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow)}
|
#inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow-card)}
|
||||||
#composer:focus-within #inputbar,#composer:focus-within #ctxmeta{border-color:var(--accent-soft)}
|
#composer:focus-within #inputbar,#composer:focus-within #ctxmeta{border-color:var(--accent-soft)}
|
||||||
#inputbar #attach,#inputbar #files-btn{width:34px;height:34px;min-height:34px;border-radius:999px;
|
#inputbar #attach,#inputbar #files-btn{width:34px;height:34px;min-height:34px;border-radius:999px;
|
||||||
align-self:center}
|
align-self:center}
|
||||||
@@ -1701,6 +1706,9 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
garde-fou de largeur (contrairement à .pe-row-c) : une valeur très longue —
|
garde-fou de largeur (contrairement à .pe-row-c) : une valeur très longue —
|
||||||
un chemin de destination, par exemple — débordait sous l'intitulé. */
|
un chemin de destination, par exemple — débordait sous l'intitulé. */
|
||||||
.pe-row>.pe-selc{max-width:62%}
|
.pe-row>.pe-selc{max-width:62%}
|
||||||
|
/* Marque et attribution du fork empilées, pastille d'état à leur droite. */
|
||||||
|
.sidebrand{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
||||||
|
.sidebrand .brand-fork{margin:0;font-size:10px;line-height:1.3}
|
||||||
</style></head><body>
|
</style></head><body>
|
||||||
<button id="menubtn" onclick="toggleSide()">☰</button>
|
<button id="menubtn" onclick="toggleSide()">☰</button>
|
||||||
<!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est
|
<!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est
|
||||||
@@ -1713,7 +1721,12 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
C'est ce découpage qui garde les jauges visibles quel que soit le nombre
|
C'est ce découpage qui garde les jauges visibles quel que soit le nombre
|
||||||
de discussions — elles les poussaient auparavant hors de l'écran. -->
|
de discussions — elles les poussaient auparavant hors de l'écran. -->
|
||||||
<div class="sidehead">
|
<div class="sidehead">
|
||||||
|
<div class="sidebrand">
|
||||||
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
|
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
|
||||||
|
<!-- Attribution du fork (licence MIT de l'amont) : sous le nom de l'app,
|
||||||
|
là où elle a toujours vécu. -->
|
||||||
|
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
|
||||||
|
</div><!-- /.sidebrand -->
|
||||||
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
|
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="side-scroll">
|
<div id="side-scroll">
|
||||||
@@ -2009,10 +2022,6 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
</details>
|
</details>
|
||||||
</div><!-- /sidegroup -->
|
</div><!-- /sidegroup -->
|
||||||
</details><!-- /#side-settings -->
|
</details><!-- /#side-settings -->
|
||||||
<!-- Attribution du fork (licence MIT de l'amont) : en pied de la zone
|
|
||||||
défilante, sous les réglages. Elle a quitté l'en-tête, que la refonte
|
|
||||||
réduit à la marque et à l'état du moteur, mais elle reste affichée. -->
|
|
||||||
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
|
|
||||||
</div><!-- /#side-scroll -->
|
</div><!-- /#side-scroll -->
|
||||||
<!-- Moniteur machine : charge GPU, VRAM et mémoire vive. Posé en pied, en
|
<!-- Moniteur machine : charge GPU, VRAM et mémoire vive. Posé en pied, en
|
||||||
jauges fines — visible en permanence sans jamais disputer la place au
|
jauges fines — visible en permanence sans jamais disputer la place au
|
||||||
@@ -2089,7 +2098,7 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
reste porté par aria-label et l'infobulle — l'action garde son nom
|
reste porté par aria-label et l'infobulle — l'action garde son nom
|
||||||
pour un lecteur d'écran, elle ne l'affiche simplement plus. -->
|
pour un lecteur d'écran, elle ne l'affiche simplement plus. -->
|
||||||
<button id="send" onclick="send()" title="Envoyer" aria-label="Envoyer">
|
<button id="send" onclick="send()" title="Envoyer" aria-label="Envoyer">
|
||||||
<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12h15"/><path d="m13 6 6 6-6 6"/></svg>
|
<svg viewBox="0 0 24 24" width="19" height="19" fill="currentColor" stroke="none" aria-hidden="true"><path d="M3.2 20.6 21.5 12 3.2 3.4l.02 6.69L16 12 3.22 13.9z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button id="stop" onclick="stopGen()" style="display:none" title="Arrêter la génération" aria-label="Arrêter la génération">
|
<button id="stop" onclick="stopGen()" style="display:none" title="Arrêter la génération" aria-label="Arrêter la génération">
|
||||||
<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" stroke="none" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" stroke="none" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
||||||
@@ -3030,6 +3039,9 @@ async function loadCfg(){
|
|||||||
else v='llama.cpp personnalisé : '+c.BIN;
|
else v='llama.cpp personnalisé : '+c.BIN;
|
||||||
rows.push(row('MOTEUR', v, c.BIN));
|
rows.push(row('MOTEUR', v, c.BIN));
|
||||||
}
|
}
|
||||||
|
// Le modèle chargé alimente aussi le sélecteur de l'en-tête : c'est la seule
|
||||||
|
// source qui le connaisse quand aucun preset ne correspond à la configuration.
|
||||||
|
if(typeof setLoadedModel === 'function') setLoadedModel(c.MODEL || '');
|
||||||
['MODEL','CTX','BATCH','UBATCH','NGL'].filter(k=>c[k]).forEach(k=>{
|
['MODEL','CTX','BATCH','UBATCH','NGL'].filter(k=>c[k]).forEach(k=>{
|
||||||
let v=c[k]; if(k==='MODEL') v=v.split('/').pop();
|
let v=c[k]; if(k==='MODEL') v=v.split('/').pop();
|
||||||
rows.push(row(k, v));
|
rows.push(row(k, v));
|
||||||
@@ -6774,33 +6786,63 @@ function onModelSwitch(sel){
|
|||||||
switchTo(n, name);
|
switchTo(n, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remplit le sélecteur à partir de la liste de presets déjà chargée par
|
// Le sélecteur affiche LE MODÈLE CHARGÉ, pas seulement le preset actif.
|
||||||
// loadPresets() — aucun appel réseau supplémentaire.
|
//
|
||||||
|
// Les deux ne coïncident pas toujours : un preset n'est « actif » que si son
|
||||||
|
// empreinte correspond exactement à la configuration courante. Une configuration
|
||||||
|
// écrite à la main, héritée d'une image, ou modifiée d'un cran depuis le dernier
|
||||||
|
// preset ne correspond à AUCUN d'entre eux — et l'en-tête n'affichait alors rien
|
||||||
|
// alors qu'un modèle était bel et bien chargé. On retombe donc sur le nom du
|
||||||
|
// fichier de la configuration active.
|
||||||
|
//
|
||||||
|
// Deux sources, deux moments : loadPresets() donne la liste, loadCfg() donne le
|
||||||
|
// modèle. Chacune dépose ce qu'elle sait et redemande le dessin — elles partent
|
||||||
|
// en parallèle (loadAll), aucun ordre n'est garanti.
|
||||||
|
let MS_PRESETS = [], MS_ACTIVE = null, MS_MODEL = '';
|
||||||
|
|
||||||
function renderModelSwitch(presets, active){
|
function renderModelSwitch(presets, active){
|
||||||
|
MS_PRESETS = presets || []; MS_ACTIVE = active || null;
|
||||||
|
paintModelSwitch();
|
||||||
|
}
|
||||||
|
// Appelé par loadCfg avec la valeur MODEL de la configuration active.
|
||||||
|
function setLoadedModel(name){
|
||||||
|
MS_MODEL = (name || '').trim();
|
||||||
|
paintModelSwitch();
|
||||||
|
}
|
||||||
|
// Nom lisible d'un fichier de modèle : sans dossier ni extension. Un .gguf
|
||||||
|
// s'appelle « Qwen3-14B-Instruct-Q4_K_M.gguf » — le chemin et le suffixe
|
||||||
|
// n'apprennent rien et mangent toute la place.
|
||||||
|
function modelLabel(path){
|
||||||
|
const base = path.split(/[\\/]/).pop();
|
||||||
|
return base.replace(/\.gguf$/i, '');
|
||||||
|
}
|
||||||
|
function paintModelSwitch(){
|
||||||
const sel = document.getElementById('model-switch');
|
const sel = document.getElementById('model-switch');
|
||||||
if(!sel) return;
|
if(!sel) return;
|
||||||
|
const prev = sel.value;
|
||||||
sel.textContent = '';
|
sel.textContent = '';
|
||||||
if(!presets || !presets.length){
|
// Ligne de tête : ce qui est RÉELLEMENT chargé, quand aucun preset ne le
|
||||||
|
// revendique. Value vide → onModelSwitch l'ignore, elle ne bascule rien.
|
||||||
|
if(!MS_ACTIVE){
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.textContent = 'aucun preset'; o.value = '';
|
o.value = '';
|
||||||
sel.appendChild(o); sel.disabled = true;
|
o.textContent = MS_MODEL ? modelLabel(MS_MODEL) : (MS_PRESETS.length ? 'aucun modèle actif' : 'aucun preset');
|
||||||
return;
|
o.selected = true;
|
||||||
|
sel.appendChild(o);
|
||||||
}
|
}
|
||||||
sel.disabled = false;
|
MS_PRESETS.forEach((p, i) => {
|
||||||
presets.forEach((p, i) => {
|
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = String(i + 1);
|
o.value = String(i + 1);
|
||||||
o.textContent = p.name;
|
o.textContent = p.name;
|
||||||
if(active && p.id === active.id) o.selected = true;
|
if(MS_ACTIVE && p.id === MS_ACTIVE.id) o.selected = true;
|
||||||
sel.appendChild(o);
|
sel.appendChild(o);
|
||||||
});
|
});
|
||||||
// Aucun preset actif (configuration modifiée à la main) : on le dit, plutôt
|
sel.disabled = !MS_PRESETS.length;
|
||||||
// que de laisser le premier de la liste passer pour le modèle chargé.
|
sel.title = MS_MODEL
|
||||||
if(!active){
|
? 'modèle chargé : ' + MS_MODEL + ' — changer de preset relance le moteur'
|
||||||
const o = document.createElement('option');
|
: 'aucun modèle configuré';
|
||||||
o.value = ''; o.textContent = 'configuration hors preset'; o.selected = true;
|
// Une bascule en cours ne doit pas voir sa sélection sauter sous la souris.
|
||||||
sel.insertBefore(o, sel.firstChild);
|
if(prev && [...sel.options].some(o => o.value === prev)) sel.value = prev;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// État initial des libellés (le CSS, lui, est déjà appliqué par le <head>).
|
// État initial des libellés (le CSS, lui, est déjà appliqué par le <head>).
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<!-- Favicon (même marque « loki » que la landing/portail) + icône PWA (écran d'accueil
|
<!-- Favicon (même marque « loki » que la landing/portail) + icône PWA (écran d'accueil
|
||||||
iOS). SVG inline pour le favicon, PNG 180×180 en data-URI pour apple-touch-icon
|
iOS). SVG inline pour le favicon, PNG 180×180 en data-URI pour apple-touch-icon
|
||||||
(iOS n'accepte pas le SVG pour l'icône d'accueil). Aucun fichier externe requis. -->
|
(iOS n'accepte pas le SVG pour l'icône d'accueil). Aucun fichier externe requis. -->
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><rect width='12' height='12' rx='2' ry='2' fill='%23000'/><rect x='6' y='3' width='2' height='2' fill='%23fff'/><rect x='6' y='5' width='2' height='2' fill='%23fff'/><rect x='4' y='7' width='2' height='2' fill='%23fff'/></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><rect width='12' height='12' rx='2' ry='2' fill='%235E7F5A'/><rect x='6' y='3' width='2' height='2' fill='%23fff'/><rect x='6' y='5' width='2' height='2' fill='%23fff'/><rect x='4' y='7' width='2' height='2' fill='%23fff'/></svg>">
|
||||||
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAABBUlEQVR42u3SAQkAAAwCQfuX3lI4GNxF0E8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIArU2NbcYhDHOIQhzgQB+JAHIgDcSAOxIE4xCEOcYhDHIgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQBziEIc4xCEOcYgDcSAOxIE4EAfiQByIQxziEIc4xIE4EAfiQByIA3EgDsQhDnGIQxziEIc4EAfiQByIAxciDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAc4hCHOMQhDnGIA3EgDsSBOBAH4kAciEMc4hCHOMQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADfLBHMhi4TnG6EAAAAAElFTkSuQmCC">
|
<link rel="apple-touch-icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAADD0lEQVR4nOzdW27aWgBGYag6ylZqh1I6lFZKppmjEylRLlyMsbG9+n3vsP2w8mfDC193d/Dj17ene5zDuv39/bif+4xZDhAwQ8wR+GRvKGJuMVXcN7+JkJnSrWGPfrGQmdPYsL+MeZGYmdvYxq76KxAyS7hmrQcvtJhZyjXtDQpazCxtaIMXgxYzazGkxbNBi5m1udTkyaDFzFqda3PU13awVkeDts6s3alGPwUtZrbiWKuuHKS8C9o6szUfm7XQpLwGbZ3ZqrftWmhSBE3Kc9CuG2zdS8MWmhRBkyJoUvbuz5RYaFIETYqgSRE0KYImRdCkCJoUQZMiaFIETYqgSRE0KYImRdCkCJoUQZMiaFIETYqgSbnLj9ez2/05PCxy7s/D90XOXYqFJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAiaFEGTImhSBE2KoEkRNCmCJkXQpAialK9LP8C9/Tk8LHLuz8P3Rc7911hoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZl/+PXt6elHwKmYqFJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgiZF0KQImhRBkyJoUgRNiqBJETQpgibly9/fj/ulHwKm8H/LFpoUQZMiaFKeg3aPZuteGrbQpAialNegXTvYqrftWmhS3gVtpdmaj81aaFI+BW2l2YpjrR5daFGzdqcadeUg5WTQVpq1Otfm2YUWNWtzqcmLVw5RsxZDWhx0hxY1Sxva4OAPhaJmKde0NypSPwXHPYwZ0VFf21lr5ja2sZvDtNZM6daxnGxphc0tpvqvP8vVQdwMMcfV9S53YYGzu9Nnr/8CAAD//xZRk2OJYiOqAAAAAElFTkSuQmCC">
|
||||||
<!-- Marque le mode PWA AVANT le rendu : la hauteur de coque et le défilement du
|
<!-- Marque le mode PWA AVANT le rendu : la hauteur de coque et le défilement du
|
||||||
document en dépendent (voir styles.css). ⚠️ On NE PEUT PAS se fier au média
|
document en dépendent (voir styles.css). ⚠️ On NE PEUT PAS se fier au média
|
||||||
`display-mode:standalone` : WebKit l'adosse au `display` du MANIFESTE, or cette
|
`display-mode:standalone` : WebKit l'adosse au `display` du MANIFESTE, or cette
|
||||||
@@ -42,7 +42,12 @@ document.documentElement.setAttribute('data-perf',localStorage.getItem('loki-per
|
|||||||
C'est ce découpage qui garde les jauges visibles quel que soit le nombre
|
C'est ce découpage qui garde les jauges visibles quel que soit le nombre
|
||||||
de discussions — elles les poussaient auparavant hors de l'écran. -->
|
de discussions — elles les poussaient auparavant hors de l'écran. -->
|
||||||
<div class="sidehead">
|
<div class="sidehead">
|
||||||
|
<div class="sidebrand">
|
||||||
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
|
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
|
||||||
|
<!-- Attribution du fork (licence MIT de l'amont) : sous le nom de l'app,
|
||||||
|
là où elle a toujours vécu. -->
|
||||||
|
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
|
||||||
|
</div><!-- /.sidebrand -->
|
||||||
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
|
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="side-scroll">
|
<div id="side-scroll">
|
||||||
@@ -338,10 +343,6 @@ document.documentElement.setAttribute('data-perf',localStorage.getItem('loki-per
|
|||||||
</details>
|
</details>
|
||||||
</div><!-- /sidegroup -->
|
</div><!-- /sidegroup -->
|
||||||
</details><!-- /#side-settings -->
|
</details><!-- /#side-settings -->
|
||||||
<!-- Attribution du fork (licence MIT de l'amont) : en pied de la zone
|
|
||||||
défilante, sous les réglages. Elle a quitté l'en-tête, que la refonte
|
|
||||||
réduit à la marque et à l'état du moteur, mais elle reste affichée. -->
|
|
||||||
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
|
|
||||||
</div><!-- /#side-scroll -->
|
</div><!-- /#side-scroll -->
|
||||||
<!-- Moniteur machine : charge GPU, VRAM et mémoire vive. Posé en pied, en
|
<!-- Moniteur machine : charge GPU, VRAM et mémoire vive. Posé en pied, en
|
||||||
jauges fines — visible en permanence sans jamais disputer la place au
|
jauges fines — visible en permanence sans jamais disputer la place au
|
||||||
@@ -418,7 +419,7 @@ document.documentElement.setAttribute('data-perf',localStorage.getItem('loki-per
|
|||||||
reste porté par aria-label et l'infobulle — l'action garde son nom
|
reste porté par aria-label et l'infobulle — l'action garde son nom
|
||||||
pour un lecteur d'écran, elle ne l'affiche simplement plus. -->
|
pour un lecteur d'écran, elle ne l'affiche simplement plus. -->
|
||||||
<button id="send" onclick="send()" title="Envoyer" aria-label="Envoyer">
|
<button id="send" onclick="send()" title="Envoyer" aria-label="Envoyer">
|
||||||
<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12h15"/><path d="m13 6 6 6-6 6"/></svg>
|
<svg viewBox="0 0 24 24" width="19" height="19" fill="currentColor" stroke="none" aria-hidden="true"><path d="M3.2 20.6 21.5 12 3.2 3.4l.02 6.69L16 12 3.22 13.9z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button id="stop" onclick="stopGen()" style="display:none" title="Arrêter la génération" aria-label="Arrêter la génération">
|
<button id="stop" onclick="stopGen()" style="display:none" title="Arrêter la génération" aria-label="Arrêter la génération">
|
||||||
<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" stroke="none" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" stroke="none" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ async function loadCfg(){
|
|||||||
else v='llama.cpp personnalisé : '+c.BIN;
|
else v='llama.cpp personnalisé : '+c.BIN;
|
||||||
rows.push(row('MOTEUR', v, c.BIN));
|
rows.push(row('MOTEUR', v, c.BIN));
|
||||||
}
|
}
|
||||||
|
// Le modèle chargé alimente aussi le sélecteur de l'en-tête : c'est la seule
|
||||||
|
// source qui le connaisse quand aucun preset ne correspond à la configuration.
|
||||||
|
if(typeof setLoadedModel === 'function') setLoadedModel(c.MODEL || '');
|
||||||
['MODEL','CTX','BATCH','UBATCH','NGL'].filter(k=>c[k]).forEach(k=>{
|
['MODEL','CTX','BATCH','UBATCH','NGL'].filter(k=>c[k]).forEach(k=>{
|
||||||
let v=c[k]; if(k==='MODEL') v=v.split('/').pop();
|
let v=c[k]; if(k==='MODEL') v=v.split('/').pop();
|
||||||
rows.push(row(k, v));
|
rows.push(row(k, v));
|
||||||
|
|||||||
@@ -67,33 +67,63 @@ function onModelSwitch(sel){
|
|||||||
switchTo(n, name);
|
switchTo(n, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remplit le sélecteur à partir de la liste de presets déjà chargée par
|
// Le sélecteur affiche LE MODÈLE CHARGÉ, pas seulement le preset actif.
|
||||||
// loadPresets() — aucun appel réseau supplémentaire.
|
//
|
||||||
|
// Les deux ne coïncident pas toujours : un preset n'est « actif » que si son
|
||||||
|
// empreinte correspond exactement à la configuration courante. Une configuration
|
||||||
|
// écrite à la main, héritée d'une image, ou modifiée d'un cran depuis le dernier
|
||||||
|
// preset ne correspond à AUCUN d'entre eux — et l'en-tête n'affichait alors rien
|
||||||
|
// alors qu'un modèle était bel et bien chargé. On retombe donc sur le nom du
|
||||||
|
// fichier de la configuration active.
|
||||||
|
//
|
||||||
|
// Deux sources, deux moments : loadPresets() donne la liste, loadCfg() donne le
|
||||||
|
// modèle. Chacune dépose ce qu'elle sait et redemande le dessin — elles partent
|
||||||
|
// en parallèle (loadAll), aucun ordre n'est garanti.
|
||||||
|
let MS_PRESETS = [], MS_ACTIVE = null, MS_MODEL = '';
|
||||||
|
|
||||||
function renderModelSwitch(presets, active){
|
function renderModelSwitch(presets, active){
|
||||||
|
MS_PRESETS = presets || []; MS_ACTIVE = active || null;
|
||||||
|
paintModelSwitch();
|
||||||
|
}
|
||||||
|
// Appelé par loadCfg avec la valeur MODEL de la configuration active.
|
||||||
|
function setLoadedModel(name){
|
||||||
|
MS_MODEL = (name || '').trim();
|
||||||
|
paintModelSwitch();
|
||||||
|
}
|
||||||
|
// Nom lisible d'un fichier de modèle : sans dossier ni extension. Un .gguf
|
||||||
|
// s'appelle « Qwen3-14B-Instruct-Q4_K_M.gguf » — le chemin et le suffixe
|
||||||
|
// n'apprennent rien et mangent toute la place.
|
||||||
|
function modelLabel(path){
|
||||||
|
const base = path.split(/[\\/]/).pop();
|
||||||
|
return base.replace(/\.gguf$/i, '');
|
||||||
|
}
|
||||||
|
function paintModelSwitch(){
|
||||||
const sel = document.getElementById('model-switch');
|
const sel = document.getElementById('model-switch');
|
||||||
if(!sel) return;
|
if(!sel) return;
|
||||||
|
const prev = sel.value;
|
||||||
sel.textContent = '';
|
sel.textContent = '';
|
||||||
if(!presets || !presets.length){
|
// Ligne de tête : ce qui est RÉELLEMENT chargé, quand aucun preset ne le
|
||||||
|
// revendique. Value vide → onModelSwitch l'ignore, elle ne bascule rien.
|
||||||
|
if(!MS_ACTIVE){
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.textContent = 'aucun preset'; o.value = '';
|
o.value = '';
|
||||||
sel.appendChild(o); sel.disabled = true;
|
o.textContent = MS_MODEL ? modelLabel(MS_MODEL) : (MS_PRESETS.length ? 'aucun modèle actif' : 'aucun preset');
|
||||||
return;
|
o.selected = true;
|
||||||
|
sel.appendChild(o);
|
||||||
}
|
}
|
||||||
sel.disabled = false;
|
MS_PRESETS.forEach((p, i) => {
|
||||||
presets.forEach((p, i) => {
|
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = String(i + 1);
|
o.value = String(i + 1);
|
||||||
o.textContent = p.name;
|
o.textContent = p.name;
|
||||||
if(active && p.id === active.id) o.selected = true;
|
if(MS_ACTIVE && p.id === MS_ACTIVE.id) o.selected = true;
|
||||||
sel.appendChild(o);
|
sel.appendChild(o);
|
||||||
});
|
});
|
||||||
// Aucun preset actif (configuration modifiée à la main) : on le dit, plutôt
|
sel.disabled = !MS_PRESETS.length;
|
||||||
// que de laisser le premier de la liste passer pour le modèle chargé.
|
sel.title = MS_MODEL
|
||||||
if(!active){
|
? 'modèle chargé : ' + MS_MODEL + ' — changer de preset relance le moteur'
|
||||||
const o = document.createElement('option');
|
: 'aucun modèle configuré';
|
||||||
o.value = ''; o.textContent = 'configuration hors preset'; o.selected = true;
|
// Une bascule en cours ne doit pas voir sa sélection sauter sous la souris.
|
||||||
sel.insertBefore(o, sel.firstChild);
|
if(prev && [...sel.options].some(o => o.value === prev)) sel.value = prev;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// État initial des libellés (le CSS, lui, est déjà appliqué par le <head>).
|
// État initial des libellés (le CSS, lui, est déjà appliqué par le <head>).
|
||||||
|
|||||||
@@ -35,7 +35,8 @@
|
|||||||
--ok:#5e7f5a;--warn:#a86a2a;--err:#c0503f;--mag:#a86a2a;
|
--ok:#5e7f5a;--warn:#a86a2a;--err:#c0503f;--mag:#a86a2a;
|
||||||
--code-bg:#f4f6f5;--danger-bg:#faeae7;--warn-bg:#f7f0e5;
|
--code-bg:#f4f6f5;--danger-bg:#faeae7;--warn-bg:#f7f0e5;
|
||||||
--add:#5e7f5a;--add-bg:rgba(94,127,90,.11);--del:#c0503f;--del-bg:rgba(192,80,63,.12);
|
--add:#5e7f5a;--add-bg:rgba(94,127,90,.11);--del:#c0503f;--del-bg:rgba(192,80,63,.12);
|
||||||
--shadow:0 1px 2px rgba(15,23,42,.05);--shadow-lift:0 4px 16px rgba(15,23,42,.08)}
|
--shadow:0 1px 2px rgba(15,23,42,.05);--shadow-lift:0 4px 16px rgba(15,23,42,.08);
|
||||||
|
--shadow-card:0 2px 10px rgba(15,23,42,.07),0 1px 3px rgba(15,23,42,.05)}
|
||||||
/* « Deep Dark » : fond #0F172A, cartes #1E293B. */
|
/* « Deep Dark » : fond #0F172A, cartes #1E293B. */
|
||||||
[data-theme="dark"]{color-scheme:dark;
|
[data-theme="dark"]{color-scheme:dark;
|
||||||
--bg:#0f172a;--panel:#1e293b;--bubble:#1e293b;--border:rgba(148,163,184,.17);
|
--bg:#0f172a;--panel:#1e293b;--bubble:#1e293b;--border:rgba(148,163,184,.17);
|
||||||
@@ -46,7 +47,8 @@
|
|||||||
--ok:#8fb08c;--warn:#d9a55f;--err:#e0705c;--mag:#d9a55f;
|
--ok:#8fb08c;--warn:#d9a55f;--err:#e0705c;--mag:#d9a55f;
|
||||||
--code-bg:#131c2f;--danger-bg:#3a1f1c;--warn-bg:#33291b;
|
--code-bg:#131c2f;--danger-bg:#3a1f1c;--warn-bg:#33291b;
|
||||||
--add:#8fb08c;--add-bg:rgba(143,176,140,.16);--del:#e0705c;--del-bg:rgba(224,112,92,.16);
|
--add:#8fb08c;--add-bg:rgba(143,176,140,.16);--del:#e0705c;--del-bg:rgba(224,112,92,.16);
|
||||||
--shadow:none;--shadow-lift:0 4px 16px rgba(0,0,0,.35)}
|
--shadow:none;--shadow-lift:0 4px 16px rgba(0,0,0,.35);
|
||||||
|
--shadow-card:0 2px 12px rgba(0,0,0,.30),0 1px 3px rgba(0,0,0,.22)}
|
||||||
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
|
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
|
||||||
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
|
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
|
||||||
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
|
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
|
||||||
@@ -1570,12 +1572,12 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
|
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
|
||||||
#chat{padding-top:22px}
|
#chat{padding-top:22px}
|
||||||
#chat .msg.assistant{background:var(--bubble);border:1px solid var(--border);
|
#chat .msg.assistant{background:var(--bubble);border:1px solid var(--border);
|
||||||
border-radius:16px;padding:14px 18px;max-width:100%;box-shadow:var(--shadow)}
|
border-radius:16px;padding:14px 18px;max-width:100%;box-shadow:var(--shadow-card)}
|
||||||
#chat .msg.assistant.errmsg{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 45%,transparent)}
|
#chat .msg.assistant.errmsg{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 45%,transparent)}
|
||||||
/* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
|
/* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
|
||||||
contraste dans les DEUX variantes. */
|
contraste dans les DEUX variantes. */
|
||||||
#chat .msg.user{background:var(--accent);color:var(--on-accent);border-radius:18px 18px 6px 18px;
|
#chat .msg.user{background:var(--accent);color:var(--on-accent);border-radius:18px 18px 6px 18px;
|
||||||
padding:11px 16px;max-width:75%}
|
padding:11px 16px;max-width:75%;box-shadow:var(--shadow-card)}
|
||||||
#chat .msg.user .label{color:var(--on-accent);opacity:.75}
|
#chat .msg.user .label{color:var(--on-accent);opacity:.75}
|
||||||
#chat .msg.user .body a{color:var(--on-accent);text-decoration:underline}
|
#chat .msg.user .body a{color:var(--on-accent);text-decoration:underline}
|
||||||
#chat .msg.user code{background:rgba(255,255,255,.18);color:inherit}
|
#chat .msg.user code{background:rgba(255,255,255,.18);color:inherit}
|
||||||
@@ -1592,13 +1594,16 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
#send,#stop{width:34px;height:34px;min-height:34px;padding:0;border-radius:999px;
|
#send,#stop{width:34px;height:34px;min-height:34px;padding:0;border-radius:999px;
|
||||||
display:inline-flex;align-items:center;justify-content:center;flex:none}
|
display:inline-flex;align-items:center;justify-content:center;flex:none}
|
||||||
#send svg,#stop svg{display:block}
|
#send svg,#stop svg{display:block}
|
||||||
#send:hover:not(:disabled){opacity:1;transform:none;background:color-mix(in srgb,var(--accent) 85%,var(--text))}
|
#send{background:transparent;border-color:transparent;color:var(--accent)}
|
||||||
|
#send:hover:not(:disabled){opacity:1;transform:none;background:var(--accent-bg);color:var(--accent)}
|
||||||
#send:active:not(:disabled){transform:scale(.92)}
|
#send:active:not(:disabled){transform:scale(.92)}
|
||||||
#stop:hover{opacity:1;background:color-mix(in srgb,var(--err) 85%,var(--text))}
|
#send:disabled{background:transparent;color:var(--dim)}
|
||||||
|
#stop{background:transparent;border-color:transparent;color:var(--err)}
|
||||||
|
#stop:hover{opacity:1;background:var(--danger-bg);color:var(--err)}
|
||||||
/* Les outils de la saisie (joindre, fichiers, envoyer) montent DANS la pilule,
|
/* Les outils de la saisie (joindre, fichiers, envoyer) montent DANS la pilule,
|
||||||
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient
|
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient
|
||||||
ce qu'il doit être — une jauge de contexte, rien d'autre. */
|
ce qu'il doit être — une jauge de contexte, rien d'autre. */
|
||||||
#inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow)}
|
#inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow-card)}
|
||||||
#composer:focus-within #inputbar,#composer:focus-within #ctxmeta{border-color:var(--accent-soft)}
|
#composer:focus-within #inputbar,#composer:focus-within #ctxmeta{border-color:var(--accent-soft)}
|
||||||
#inputbar #attach,#inputbar #files-btn{width:34px;height:34px;min-height:34px;border-radius:999px;
|
#inputbar #attach,#inputbar #files-btn{width:34px;height:34px;min-height:34px;border-radius:999px;
|
||||||
align-self:center}
|
align-self:center}
|
||||||
@@ -1670,3 +1675,6 @@ button:active:not(:disabled){transform:translateY(1px)}
|
|||||||
garde-fou de largeur (contrairement à .pe-row-c) : une valeur très longue —
|
garde-fou de largeur (contrairement à .pe-row-c) : une valeur très longue —
|
||||||
un chemin de destination, par exemple — débordait sous l'intitulé. */
|
un chemin de destination, par exemple — débordait sous l'intitulé. */
|
||||||
.pe-row>.pe-selc{max-width:62%}
|
.pe-row>.pe-selc{max-width:62%}
|
||||||
|
/* Marque et attribution du fork empilées, pastille d'état à leur droite. */
|
||||||
|
.sidebrand{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
||||||
|
.sidebrand .brand-fork{margin:0;font-size:10px;line-height:1.3}
|
||||||
Reference in new issue
Block a user