mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 10:59:32 +01:00
feat(local-model): types and module skeleton for the hardware-aware recommendation
The contract three builders code against in parallel. Nothing is recommended yet: the route answers 501 and the catalogue is empty. - src/types/local-model.ts: HardwareFacts, the catalogue rows, HarnessFact, Candidate, Recommendation, LocalModelStatus, and LOCAL_MODEL_LABELS, the one list of sentences the server may put into the payload (the zh-CN dictionary is keyed by them). - src/local-model/format.ts: formatGb, the one place a byte count becomes a size a person reads (decimal GB), and memClassGb. - src/config/local-model-catalog.ts: the Zod schema (model ids are name:tag, lowercase, no slash) and an empty stock catalogue. No override file. - Skeletons with their final signatures: hardware.ts, recommend.ts, harness-facts.ts, routes/local-model-routes.ts (injectable dependencies), public/local-model-ui.js with its script tag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
e9b2ecd552
commit
73ce6a1819
12 files changed
+824
No files matched your search
@@ -4685,6 +4685,7 @@
|
||||
<script defer src="foreign-sessions.js"></script>
|
||||
<script defer src="git-status-ui.js"></script>
|
||||
<script defer src="edit-history-ui.js"></script>
|
||||
<script defer src="local-model-ui.js"></script>
|
||||
<script defer src="entrance-animations.js"></script>
|
||||
<script defer src="ralph-wizard.js"></script>
|
||||
<script defer src="api-client.js"></script>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @fileoverview The "model on this Mac" card of the welcome screen and its row in App Settings.
|
||||
*
|
||||
* SKELETON: this file holds no code yet. It will read `GET /api/local-model` (hardware of the
|
||||
* machine the server runs on, the recommendation, the catalogue date) and show, read-only:
|
||||
* - the hardware line (chip, memory, free disk);
|
||||
* - the verdict: the headline and notes the server sent, then the pick with its numbers
|
||||
* (model, harness, context, memory need against the budget, download size, speed);
|
||||
* - the other models that fit, each with its fit and speed label;
|
||||
* - one line saying that setting the model up from Codeman is the next step and is not built
|
||||
* yet.
|
||||
*
|
||||
* Rules:
|
||||
* - Read-only: no button here downloads, installs or writes anything.
|
||||
* - Hidden when the server is not on macOS (`recommendation.reason` is `not-macos`), when the
|
||||
* request is refused (a non-admin in multi-user mode gets a 403) and after the user dismissed
|
||||
* the card on this device (a localStorage key, never a server setting).
|
||||
* - Every dynamic string goes in through `textContent`. Model, harness and chip names are
|
||||
* someone else's text and carry `data-i18n-skip`.
|
||||
* - Names and labels come from the payload: this file holds no list of models or harnesses and
|
||||
* never compares a CLI id with a literal.
|
||||
* - Sizes are printed exactly as sent (`needGb`, `downloadGb`, `budgetGb`, `memGb`,
|
||||
* `freeDiskGb`: decimal GB as text). Bytes are never divided here.
|
||||
* - A sentence the server chose arrives as `{ text, vars }` and goes through
|
||||
* `codemanT(text, vars)`; this file's own strings are literals in its translator call, so the
|
||||
* i18n test can harvest them. Both kinds need a zh-CN entry in i18n.js.
|
||||
*
|
||||
* @mixin Extends CodemanApp.prototype via Object.assign
|
||||
* @dependency app.js (CodemanApp)
|
||||
* @dependency api-client.js (_api)
|
||||
* @dependency i18n.js (codemanT, optional)
|
||||
* @loadorder 12.59 of 16, after edit-history-ui.js, before entrance-animations.js
|
||||
*/
|
||||
@@ -49,5 +49,6 @@ export {
|
||||
type CustomModelSwapDisplacement,
|
||||
} from './custom-model-routes.js';
|
||||
export { registerCliRegistryRoutes, readCliManagementEnabled, type CliListItem } from './cli-registry-routes.js';
|
||||
export { registerLocalModelRoutes } from './local-model-routes.js';
|
||||
|
||||
export { registerAgentSessionRoutes } from './agent-session-routes.js';
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @fileoverview `GET /api/local-model`: what this Mac is and which local model fits it.
|
||||
*
|
||||
* SKELETON: the route is registered and answers 501 until its handler is written. It will
|
||||
* answer `{ success: true, data: LocalModelStatus }`, built from the hardware probe, the
|
||||
* catalogue, the harness facts and the pure `recommend()`:
|
||||
* `{ hardware, recommendation, catalogUpdated }`.
|
||||
*
|
||||
* Rules:
|
||||
* - Read-only. It downloads nothing, installs nothing, writes no file and reads no setting.
|
||||
* - Never a 500 for a probe that failed: a failed probe is an answer (the hardware's `source`
|
||||
* says so and the recommendation is verdict `none`).
|
||||
* - The payload names hardware of the host, so in multi-user mode it is admin only (403), the
|
||||
* same bar as `GET /api/doctor`.
|
||||
* - Every dependency is injected (`LocalModelRouteDeps`), so a route test spawns nothing and
|
||||
* does not need a Mac: it passes its own hardware and harness facts.
|
||||
*/
|
||||
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import {
|
||||
ApiErrorCode,
|
||||
createErrorResponse,
|
||||
type ApiResponse,
|
||||
type HardwareFacts,
|
||||
type HarnessFact,
|
||||
type LocalModelCatalog,
|
||||
type LocalModelStatus,
|
||||
type Recommendation,
|
||||
} from '../../types.js';
|
||||
import { getLocalModelCatalog } from '../../config/local-model-catalog.js';
|
||||
import { readHardware } from '../../local-model/hardware.js';
|
||||
import { buildHarnessFacts } from '../../local-model/harness-facts.js';
|
||||
import { recommend } from '../../local-model/recommend.js';
|
||||
|
||||
/** What the route calls. Each member defaults to the real module; a test overrides what it needs. */
|
||||
export interface LocalModelRouteDeps {
|
||||
readHardware: () => Promise<HardwareFacts>;
|
||||
getCatalog: () => LocalModelCatalog;
|
||||
buildHarnessFacts: (catalog: LocalModelCatalog) => Promise<HarnessFact[]>;
|
||||
recommend: (hw: HardwareFacts, catalog: LocalModelCatalog, harnessFacts: readonly HarnessFact[]) => Recommendation;
|
||||
}
|
||||
|
||||
export const defaultLocalModelRouteDeps: LocalModelRouteDeps = {
|
||||
readHardware: () => readHardware(),
|
||||
getCatalog: getLocalModelCatalog,
|
||||
buildHarnessFacts,
|
||||
recommend,
|
||||
};
|
||||
|
||||
export function registerLocalModelRoutes(app: FastifyInstance, deps: Partial<LocalModelRouteDeps> = {}): void {
|
||||
const resolved: LocalModelRouteDeps = { ...defaultLocalModelRouteDeps, ...deps };
|
||||
void resolved;
|
||||
app.get(
|
||||
'/api/local-model',
|
||||
async (_req: FastifyRequest, reply: FastifyReply): Promise<ApiResponse<LocalModelStatus>> => {
|
||||
reply.code(501);
|
||||
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'GET /api/local-model is not implemented yet');
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -264,6 +264,7 @@ import {
|
||||
pruneIdleLlamaSwapLogTails,
|
||||
tryWebviewRefererFallback,
|
||||
registerCliRegistryRoutes,
|
||||
registerLocalModelRoutes,
|
||||
} from './routes/index.js';
|
||||
import { isLostWebviewFrameNavigation } from './webview-proxy.js';
|
||||
import { CronService } from '../cron/cron-service.js';
|
||||
@@ -1417,6 +1418,7 @@ export class WebServer extends EventEmitter {
|
||||
registerDoctorRoutes(this.app);
|
||||
registerCustomModelRoutes(this.app);
|
||||
registerCliRegistryRoutes(this.app);
|
||||
registerLocalModelRoutes(this.app);
|
||||
|
||||
// Cron: build the service from the same context, recompute
|
||||
// due times for any persisted jobs, then expose it to its routes.
|
||||
|
||||
Reference in new issue
Block a user