Compare commits

...
1 Commits
6 changed files with 30 additions and 10 deletions

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "eveflow", "name": "eveflow",
"version": "2.5.2", "version": "2.5.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "eveflow", "name": "eveflow",
"version": "2.5.2", "version": "2.5.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/orbitron": "^5.3.0", "@fontsource/orbitron": "^5.3.0",
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "eveflow", "name": "eveflow",
"version": "2.5.2", "version": "2.5.3",
"releaseVersion": "2.5.2", "releaseVersion": "2.5.3",
"description": "JARVIS-style desktop HUD for Hermes Agent: voice, streaming runs, scheduled jobs, skills and telemetry", "description": "JARVIS-style desktop HUD for Hermes Agent: voice, streaming runs, scheduled jobs, skills and telemetry",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"private": true, "private": true,
+3 -2
View File
@@ -638,8 +638,9 @@ mcp_servers:
<div className="card"> <div className="card">
<div className="grid-2"> <div className="grid-2">
<div className="field"> <div className="field">
<label>Nom de l’assistant</label> <label htmlFor="assistant-name">Nom de l’assistant</label>
<input className="input" value={settings.assistantName} onChange={(e) => update({ assistantName: e.target.value.toUpperCase().slice(0, 18) || 'JARVIS' })} /> <input id="assistant-name" className="input" value={settings.assistantName} maxLength={60} placeholder="Jarvis, Nova, Alfred…" onChange={(e) => update({ assistantName: e.target.value })} onBlur={() => update({ assistantName: settings.assistantName.trim() || DEFAULT_SETTINGS.assistantName })} />
<span className="hint">Choisissez le nom que vous voulez. Il est sauvegardé automatiquement et utilisé par l’assistant dès votre prochain message, y compris dans la conversation en cours.</span>
</div> </div>
<div className="field"> <div className="field">
<label>Votre nom</label> <label>Votre nom</label>
+5 -3
View File
@@ -108,10 +108,12 @@ export const useHermes = create<HermesStore>((set, get) => ({
busy: false, busy: false,
client: (modelOverride) => { client: (modelOverride) => {
const config = useSettings.getState().settings.hermes; const settings = useSettings.getState().settings;
// Without an explicit model, use the alias advertised by /v1/models (Hermes rejects unknown names). const config = settings.hermes;
const model = (modelOverride ?? '').trim() || config.model.trim(); const model = (modelOverride ?? '').trim() || config.model.trim();
return new HermesClient({ ...config, model }); const name = settings.assistantName.trim() || 'JARVIS';
const identity = `Identité de l'assistant dans cette conversation : ton nom est ${JSON.stringify(name)}. Utilise ce nom pour te présenter et parler de toi. EveFlow est le nom de l'application, pas ton nom. Cette identité remplace les anciens noms ou personas présents dans l'historique, la mémoire ou les instructions précédentes. Ne rappelle pas ton nom dans chaque réponse.`;
return new HermesClient({ ...config, model, instructions: [config.instructions.trim(), identity].filter(Boolean).join('\n\n') });
}, },
connect: () => { connect: () => {
+1 -1
View File
@@ -89,7 +89,7 @@ export const DEFAULT_SETTINGS: Settings = {
transport: 'auto', transport: 'auto',
reasoningEffort: '', reasoningEffort: '',
instructions: instructions:
"Tu es l'interface vocale EveFlow (style JARVIS). Réponds en français, de façon concise et orale quand la question est simple; utilise le Markdown uniquement pour le contenu structuré (code, listes, tableaux). Les images doivent être des URL http(s) ou des fichiers du dossier partagé.", "Réponds en français, de façon concise et orale quand la question est simple; utilise le Markdown uniquement pour le contenu structuré (code, listes, tableaux). Les images doivent être des URL http(s) ou des fichiers du dossier partagé.",
localTools: true, localTools: true,
missionModel: '' missionModel: ''
}, },
+17
View File
@@ -15,6 +15,23 @@ function respond(payload: unknown, status = 200) {
afterEach(() => { vi.restoreAllMocks(); useSettings.setState({ settings: DEFAULT_SETTINGS }); }); afterEach(() => { vi.restoreAllMocks(); useSettings.setState({ settings: DEFAULT_SETTINGS }); });
describe('Hermes models', () => { describe('Hermes models', () => {
it.each(['runs', 'sessions', 'completions'] as const)('sends the configured assistant identity over %s, including after a rename', async (transport) => {
const requests: Record<string, unknown>[] = [];
const capture = async (req: { body?: string }) => { requests.push(JSON.parse(req.body!)); throw new Error('stop'); };
vi.mocked(httpFetch).mockImplementation(capture);
vi.mocked(httpStream).mockImplementation(capture);
for (const name of ['Jarvis', 'Nova']) {
useSettings.setState({ settings: { ...DEFAULT_SETTINGS, assistantName: name, hermes: { ...config, instructions: 'Tu es Eve. Réponds en français.' } } });
const send = useHermes.getState().client().send({ text: 'Qui es-tu ?', sessionId: 'hs:test', history: [], onEvent: vi.fn() }, transport);
await expect(send.result).rejects.toThrow('stop');
const body = requests.at(-1)!;
const instructions = transport === 'completions' ? (body.messages as { content: string }[])[0].content : body.instructions as string;
expect(instructions).toContain(`ton nom est "${name}"`);
expect(instructions).toContain('Réponds en français.');
expect(instructions).toContain("EveFlow est le nom de l'application, pas ton nom");
}
expect(useSettings.getState().settings.hermes.instructions).toBe('Tu es Eve. Réponds en français.');
});
it('reads the real provider inventory, keeps providers distinct and marks inaccessible models', async () => { it('reads the real provider inventory, keeps providers distinct and marks inaccessible models', async () => {
respond({ providers: [ respond({ providers: [
{ slug: 'first', name: 'First', authenticated: true, models: ['same', 'locked'], unavailable_models: ['locked'] }, { slug: 'first', name: 'First', authenticated: true, models: ['same', 'locked'], unavailable_models: ['locked'] },