fix: adapt model enhancements for DSH 0.1.5
This commit is contained in:
+62
-2
@@ -1,5 +1,8 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { apply } from '../src/index';
|
||||
import { apply as applyHost } from '../src/index';
|
||||
import * as clientModule from '../src/client/index';
|
||||
import { ENHANCER_CSS } from '../src/client/styles';
|
||||
import React from 'react';
|
||||
|
||||
describe('Host plugin', () => {
|
||||
it('registers models dev exact proxy route', () => {
|
||||
@@ -11,10 +14,67 @@ describe('Host plugin', () => {
|
||||
},
|
||||
};
|
||||
|
||||
apply(mockCtx as any);
|
||||
applyHost(mockCtx as any);
|
||||
expect(registerMock).toHaveBeenCalled();
|
||||
const callArg = registerMock.mock.calls[0][0];
|
||||
expect(callArg.path).toBe('/api/models-dev/models');
|
||||
expect(callArg.kind).toBe('exact');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Client plugin contracts', () => {
|
||||
it('registers settings extension slots only and leaves model selection untouched', async () => {
|
||||
const registeredSlots: string[] = [];
|
||||
const registeredEntries: any[] = [];
|
||||
|
||||
const mockScope = {
|
||||
slots: {
|
||||
inject: (slotName: string, cb: any) => {
|
||||
registeredSlots.push(slotName);
|
||||
cb();
|
||||
},
|
||||
register: vi.fn((def: any, component: any) => {
|
||||
registeredEntries.push({ def, component });
|
||||
return def;
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
const mockCtx = {
|
||||
inject: vi.fn((deps: string[], cb: any) => {
|
||||
cb(mockScope);
|
||||
}),
|
||||
get: vi.fn(),
|
||||
};
|
||||
|
||||
clientModule.apply(mockCtx as any);
|
||||
|
||||
// Verify slots registered
|
||||
expect(registeredSlots).toContain('settings.models.footer');
|
||||
expect(registeredSlots).toContain('settings.models.provider-card');
|
||||
// Ensure no composer / model-selection slots are registered
|
||||
expect(registeredSlots).not.toContain('conversation.input.right');
|
||||
expect(registeredSlots).not.toContain('conversation.input.model');
|
||||
|
||||
// Verify footer registration definition
|
||||
const footerEntry = registeredEntries.find(e => e.def.name === 'settings.models.footer');
|
||||
expect(footerEntry).toBeDefined();
|
||||
expect(footerEntry.def.id).toBe('me-models-footer');
|
||||
|
||||
// Verify provider card registration definition
|
||||
const cardEntry = registeredEntries.find(e => e.def.name === 'settings.models.provider-card');
|
||||
expect(cardEntry).toBeDefined();
|
||||
expect(cardEntry.def.key).toBe('llm-pi-ai');
|
||||
});
|
||||
|
||||
it('ENHANCER_CSS contains only non-selector settings styles', () => {
|
||||
expect(ENHANCER_CSS).toContain('.me-footer-card');
|
||||
expect(ENHANCER_CSS).toContain('.me-footer-title');
|
||||
expect(ENHANCER_CSS).toContain('.me-diff-table');
|
||||
// Ensure no selector styles or hide rules remain
|
||||
expect(ENHANCER_CSS).not.toContain('me-compact');
|
||||
expect(ENHANCER_CSS).not.toContain('me-slider');
|
||||
expect(ENHANCER_CSS).not.toContain('me-effort');
|
||||
expect(ENHANCER_CSS).not.toContain('display: none');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
createElement: () => ({}),
|
||||
Fragment: () => ({}),
|
||||
useState: (initial: any) => [initial, () => {}],
|
||||
useEffect: () => {},
|
||||
useLayoutEffect: () => {},
|
||||
useMemo: (fn: any) => fn(),
|
||||
useRef: (val: any) => ({ current: val }),
|
||||
useSyncExternalStore: () => ({})
|
||||
};
|
||||
export const createElement = () => ({});
|
||||
export const jsx = () => ({});
|
||||
export const jsxs = () => ({});
|
||||
export const jsxDEV = () => ({});
|
||||
export const Fragment = () => ({});
|
||||
export const useState = (initial: any) => [initial, () => {}];
|
||||
export const useEffect = () => {};
|
||||
export const useLayoutEffect = () => {};
|
||||
export const useMemo = (fn: any) => fn();
|
||||
export const useRef = (val: any) => ({ current: val });
|
||||
export const useSyncExternalStore = () => ({});
|
||||
+109
-3
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { normModelKey, findDevModel } from '../src/client/modelsDev';
|
||||
import { normModelKey, findDevModel, sanitizePositiveInt, mergeModelCapabilities } from '../src/client/modelsDev';
|
||||
import { I18N_DICT } from '../src/client/i18n';
|
||||
|
||||
describe('modelsDev utils', () => {
|
||||
@@ -21,12 +21,118 @@ describe('modelsDev utils', () => {
|
||||
expect(findDevModel('deepseek-r1', list)?.id).toBe('deepseek/deepseek-r1');
|
||||
});
|
||||
|
||||
it('sanitizes positive integers correctly and filters non-positive or non-finite values', () => {
|
||||
expect(sanitizePositiveInt(128000)).toBe(128000);
|
||||
expect(sanitizePositiveInt(1)).toBe(1);
|
||||
expect(sanitizePositiveInt('4096')).toBe(4096);
|
||||
|
||||
expect(sanitizePositiveInt(0)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(-10)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(NaN)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(Infinity)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(-Infinity)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(128.5)).toBeUndefined();
|
||||
expect(sanitizePositiveInt('0')).toBeUndefined();
|
||||
expect(sanitizePositiveInt('-1')).toBeUndefined();
|
||||
expect(sanitizePositiveInt('abc')).toBeUndefined();
|
||||
expect(sanitizePositiveInt(null)).toBeUndefined();
|
||||
expect(sanitizePositiveInt(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('merges model capabilities without writing zero or invalid contextWindow/maxTokens', () => {
|
||||
const matchedWithZero = {
|
||||
id: 'test/model-zero',
|
||||
rawKey: 'model-zero',
|
||||
name: 'Model Zero',
|
||||
limit: {
|
||||
context: 0,
|
||||
output: 0
|
||||
},
|
||||
modalities: { input: ['text'] },
|
||||
reasoning: false
|
||||
};
|
||||
|
||||
const targetModel = {
|
||||
id: 'model-zero',
|
||||
name: 'Existing Name'
|
||||
};
|
||||
|
||||
const merged = mergeModelCapabilities(targetModel, matchedWithZero as any);
|
||||
expect(merged.id).toBe('model-zero');
|
||||
expect(merged.name).toBe('Existing Name');
|
||||
expect(merged.contextWindow).toBeUndefined();
|
||||
expect('contextWindow' in merged).toBe(false);
|
||||
expect(merged.maxTokens).toBeUndefined();
|
||||
expect('maxTokens' in merged).toBe(false);
|
||||
});
|
||||
|
||||
it('preserves valid existing contextWindow/maxTokens when incoming is zero or missing', () => {
|
||||
const matchedWithZero = {
|
||||
id: 'test/model-zero',
|
||||
rawKey: 'model-zero',
|
||||
name: 'Model Zero',
|
||||
limit: {
|
||||
context: 0,
|
||||
output: 0
|
||||
}
|
||||
};
|
||||
|
||||
const existingModel = {
|
||||
id: 'model-zero',
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192
|
||||
};
|
||||
|
||||
const merged = mergeModelCapabilities(existingModel, matchedWithZero as any);
|
||||
expect(merged.contextWindow).toBe(131072);
|
||||
expect(merged.maxTokens).toBe(8192);
|
||||
});
|
||||
|
||||
it('populates valid positive contextWindow/maxTokens from models.dev when existing is missing', () => {
|
||||
const matchedValid = {
|
||||
id: 'openai/gpt-4o',
|
||||
rawKey: 'gpt-4o',
|
||||
name: 'GPT-4o',
|
||||
limit: {
|
||||
context: 128000,
|
||||
output: 16384
|
||||
}
|
||||
};
|
||||
|
||||
const existingModel = {
|
||||
id: 'gpt-4o'
|
||||
};
|
||||
|
||||
const merged = mergeModelCapabilities(existingModel, matchedValid as any);
|
||||
expect(merged.contextWindow).toBe(128000);
|
||||
expect(merged.maxTokens).toBe(16384);
|
||||
});
|
||||
|
||||
it('cleans up invalid existing values if present and matched is missing or zero', () => {
|
||||
const matchedWithZero = {
|
||||
id: 'test/model-bad',
|
||||
rawKey: 'model-bad',
|
||||
limit: {
|
||||
context: 0,
|
||||
output: -1
|
||||
}
|
||||
};
|
||||
|
||||
const existingModelWithZero = {
|
||||
id: 'model-bad',
|
||||
contextWindow: 0,
|
||||
maxTokens: -5
|
||||
};
|
||||
|
||||
const merged = mergeModelCapabilities(existingModelWithZero, matchedWithZero as any);
|
||||
expect('contextWindow' in merged).toBe(false);
|
||||
expect('maxTokens' in merged).toBe(false);
|
||||
});
|
||||
|
||||
it('provides bilingual dictionary keys', () => {
|
||||
expect(I18N_DICT.zh.modalTitle).toBeDefined();
|
||||
expect(I18N_DICT.en.modalTitle).toBeDefined();
|
||||
expect(I18N_DICT.zh.batchBtn).toBeDefined();
|
||||
expect(I18N_DICT.en.batchBtn).toBeDefined();
|
||||
expect(I18N_DICT.zh.effortReasoningTitle).toBeDefined();
|
||||
expect(I18N_DICT.en.effortReasoningTitle).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user