chat: rename the pi chat transport + model list to chat (Stage 4b/1)
Pure rename, no behavior change. Moves the misnamed "pi" chat harness into the
chat namespace:
- api/pi/{websocket,session-manager,types,logger,list-models} → api/chat/
- merge api/pi/rest.ts into api/chat/chat.ts (/pi/models → /chat/models,
/pi/stt → /chat/stt); drop the piRestRouter mount
- PiEvent → ChatEvent, piWebsocket → chatWebsocket, listPiModels → listChatModels
- WS route /api/pi/chat/ws → /api/chat/ws, provider tag 'pi' → 'chat'
- frontend: useChat/useAudioRecording URLs, usePiModels→useModels /
useVisiblePiModels→useVisibleModels / useEnabledPiModels→useEnabledModels,
'PI_MODELS' query key → 'CHAT_MODELS', attachments provider 'pi-mono' → 'chat'
The /pi-mono provider/harness settings router is renamed separately (next commit).
Note: the WS route change requires the mobile app to point at /api/chat/ws.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { useAuth } from 'hooks/useAuth';
|
|||||||
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||||
import { useSettings } from 'state/useSettings';
|
import { useSettings } from 'state/useSettings';
|
||||||
import type { UserSettings } from 'state/useSettings';
|
import type { UserSettings } from 'state/useSettings';
|
||||||
import { usePiModels, useVisiblePiModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels';
|
import { useModels, useVisibleModels, modelKey, getProviderDisplayName, type ModelOption } from 'state/useModels';
|
||||||
import { useAccessPolicy } from 'state/useAccessPolicy';
|
import { useAccessPolicy } from 'state/useAccessPolicy';
|
||||||
import { useUserState } from 'state/useUserState';
|
import { useUserState } from 'state/useUserState';
|
||||||
import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection';
|
import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection';
|
||||||
@@ -106,8 +106,8 @@ const DropZone = ({ label, children, onDrop }: DropZoneProps) => {
|
|||||||
function MyModelsSection() {
|
function MyModelsSection() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { settings, saveSettings } = useSettings();
|
const { settings, saveSettings } = useSettings();
|
||||||
const allModels = usePiModels();
|
const allModels = useModels();
|
||||||
const policyModels = useVisiblePiModels();
|
const policyModels = useVisibleModels();
|
||||||
const isAdmin = user?.role !== 'Member';
|
const isAdmin = user?.role !== 'Member';
|
||||||
const visibleModels = isAdmin ? allModels : policyModels;
|
const visibleModels = isAdmin ? allModels : policyModels;
|
||||||
const [activeProvider, setActiveProvider] = useUserState<string>('my-models-provider', '');
|
const [activeProvider, setActiveProvider] = useUserState<string>('my-models-provider', '');
|
||||||
@@ -244,7 +244,7 @@ function MemberModelsSection() {
|
|||||||
const client = useClient();
|
const client = useClient();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { policy, savePolicy } = useAccessPolicy();
|
const { policy, savePolicy } = useAccessPolicy();
|
||||||
const piModels = usePiModels();
|
const piModels = useModels();
|
||||||
const [activeProvider, setActiveProvider] = useUserState<string>('member-models-provider', '');
|
const [activeProvider, setActiveProvider] = useUserState<string>('member-models-provider', '');
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ function MemberModelsSection() {
|
|||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
try {
|
try {
|
||||||
await client.post('/server-settings/pi-mono/local-providers/refresh');
|
await client.post('/server-settings/pi-mono/local-providers/refresh');
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] });
|
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||||
toast.success('Models refreshed');
|
toast.success('Models refreshed');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Failed to refresh models');
|
toast.error('Failed to refresh models');
|
||||||
@@ -402,7 +402,7 @@ function MemberModelsSection() {
|
|||||||
|
|
||||||
// function ChatDefaultsSection() {
|
// function ChatDefaultsSection() {
|
||||||
// const { settings, saveSettings } = useSettings();
|
// const { settings, saveSettings } = useSettings();
|
||||||
// const piModels = useVisiblePiModels();
|
// const piModels = useVisibleModels();
|
||||||
// const [isSaving, setIsSaving] = useState(false);
|
// const [isSaving, setIsSaving] = useState(false);
|
||||||
// const [model, setModel] = useState<string | null>(settings.chat.defaultModel);
|
// const [model, setModel] = useState<string | null>(settings.chat.defaultModel);
|
||||||
// const [systemPrompt, setSystemPrompt] = useState(settings.chat.systemPrompt);
|
// const [systemPrompt, setSystemPrompt] = useState(settings.chat.systemPrompt);
|
||||||
|
|||||||
+4
-4
@@ -117,7 +117,7 @@ export const AIHarnessesSection = () => {
|
|||||||
await client.put('/server-settings/pi-mono/api-keys', { provider: piId, value });
|
await client.put('/server-settings/pi-mono/api-keys', { provider: piId, value });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] });
|
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||||
setKeyInputs((prev) => {
|
setKeyInputs((prev) => {
|
||||||
const next = { ...prev };
|
const next = { ...prev };
|
||||||
delete next[piId];
|
delete next[piId];
|
||||||
@@ -133,7 +133,7 @@ export const AIHarnessesSection = () => {
|
|||||||
await client.put('/server-settings/pi-mono/api-keys', { provider: provider.piId, value: '' });
|
await client.put('/server-settings/pi-mono/api-keys', { provider: provider.piId, value: '' });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_API_KEYS'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_REMOTE_HEALTH'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] });
|
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||||
if (editingProvider === provider.key) setEditingProvider(null);
|
if (editingProvider === provider.key) setEditingProvider(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ export const AIHarnessesSection = () => {
|
|||||||
});
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] });
|
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||||
toast.success(`Connected to ${probe.name}`);
|
toast.success(`Connected to ${probe.name}`);
|
||||||
resetLocalForm();
|
resetLocalForm();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -230,7 +230,7 @@ export const AIHarnessesSection = () => {
|
|||||||
await client.delete(`/server-settings/pi-mono/local-providers/${id}`);
|
await client.delete(`/server-settings/pi-mono/local-providers/${id}`);
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_PROVIDERS'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
queryClient.invalidateQueries({ queryKey: ['PI_MONO_LOCAL_HEALTH'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['PI_MODELS'] });
|
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { usePlans } from 'state/usePlans';
|
import { usePlans } from 'state/usePlans';
|
||||||
import { useSettings } from 'state/useSettings';
|
import { useSettings } from 'state/useSettings';
|
||||||
import { usePiModels } from 'state/useModels';
|
import { useModels } from 'state/useModels';
|
||||||
import { useAccessPolicy } from 'state/useAccessPolicy';
|
import { useAccessPolicy } from 'state/useAccessPolicy';
|
||||||
import { useColorModeSync } from './useThemeSync';
|
import { useColorModeSync } from './useThemeSync';
|
||||||
|
|
||||||
export const useInitialData = () => {
|
export const useInitialData = () => {
|
||||||
const { plans } = usePlans();
|
const { plans } = usePlans();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
usePiModels();
|
useModels();
|
||||||
useAccessPolicy();
|
useAccessPolicy();
|
||||||
useColorModeSync();
|
useColorModeSync();
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -5,7 +5,7 @@ import { honoServer } from './servers/hono';
|
|||||||
import { verify } from './servers/jwt';
|
import { verify } from './servers/jwt';
|
||||||
import { isTokenBlacklisted } from 'officerdb';
|
import { isTokenBlacklisted } from 'officerdb';
|
||||||
import { terminalWebsocket } from './servers/api/terminal/websocket';
|
import { terminalWebsocket } from './servers/api/terminal/websocket';
|
||||||
import { piWebsocket } from './servers/api/pi/websocket';
|
import { chatWebsocket } from './servers/api/chat/websocket';
|
||||||
import { taskRunnerWebsocket } from './servers/api/tasks/task-executor';
|
import { taskRunnerWebsocket } from './servers/api/tasks/task-executor';
|
||||||
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
import { pipelineWebsocket } from './servers/api/tasks/pipeline-executor';
|
||||||
import { cliampWebsocket } from './servers/api/cliamp/websocket';
|
import { cliampWebsocket } from './servers/api/cliamp/websocket';
|
||||||
@@ -33,7 +33,7 @@ type WSData = {
|
|||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
role: string;
|
role: string;
|
||||||
provider: 'terminal' | 'pi' | 'task-runner' | 'pipeline' | 'dev-server' | 'cliamp' | 'cliamp-audio' | 'desktop' | 'sidecar';
|
provider: 'terminal' | 'chat' | 'task-runner' | 'pipeline' | 'dev-server' | 'cliamp' | 'cliamp-audio' | 'desktop' | 'sidecar';
|
||||||
sandboxed: boolean;
|
sandboxed: boolean;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
@@ -131,7 +131,7 @@ async function handleSidecarQueueCommand(ws: ServerWebSocket<WSData>, msg: Recor
|
|||||||
|
|
||||||
const handlers: Record<string, any> = {
|
const handlers: Record<string, any> = {
|
||||||
terminal: terminalWebsocket,
|
terminal: terminalWebsocket,
|
||||||
pi: piWebsocket,
|
chat: chatWebsocket,
|
||||||
'task-runner': taskRunnerWebsocket,
|
'task-runner': taskRunnerWebsocket,
|
||||||
pipeline: pipelineWebsocket,
|
pipeline: pipelineWebsocket,
|
||||||
cliamp: cliampWebsocket,
|
cliamp: cliampWebsocket,
|
||||||
@@ -205,7 +205,7 @@ const devServerWebsocket = {
|
|||||||
};
|
};
|
||||||
handlers['dev-server'] = devServerWebsocket;
|
handlers['dev-server'] = devServerWebsocket;
|
||||||
|
|
||||||
async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'pi' | 'task-runner' | 'pipeline' | 'cliamp' | 'cliamp-audio' | 'desktop') {
|
async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'chat' | 'task-runner' | 'pipeline' | 'cliamp' | 'cliamp-audio' | 'desktop') {
|
||||||
const token = new URL(req.url).searchParams.get('token');
|
const token = new URL(req.url).searchParams.get('token');
|
||||||
if (!token) return new Response('Unauthorized', { status: 401 });
|
if (!token) return new Response('Unauthorized', { status: 401 });
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ const server = serve({
|
|||||||
'/api/tasks/run/ws': (req, server) => upgradeWs(req, server, 'task-runner'),
|
'/api/tasks/run/ws': (req, server) => upgradeWs(req, server, 'task-runner'),
|
||||||
'/api/tasks/pipeline/ws': (req, server) => upgradeWs(req, server, 'pipeline'),
|
'/api/tasks/pipeline/ws': (req, server) => upgradeWs(req, server, 'pipeline'),
|
||||||
'/api/terminal/ws': (req, server) => upgradeWs(req, server, 'terminal'),
|
'/api/terminal/ws': (req, server) => upgradeWs(req, server, 'terminal'),
|
||||||
'/api/pi/chat/ws': (req, server) => upgradeWs(req, server, 'pi'),
|
'/api/chat/ws': (req, server) => upgradeWs(req, server, 'chat'),
|
||||||
'/api/cliamp/ws': (req, server) => upgradeWs(req, server, 'cliamp'),
|
'/api/cliamp/ws': (req, server) => upgradeWs(req, server, 'cliamp'),
|
||||||
'/api/cliamp/audio/ws': (req, server) => upgradeWs(req, server, 'cliamp-audio'),
|
'/api/cliamp/audio/ws': (req, server) => upgradeWs(req, server, 'cliamp-audio'),
|
||||||
'/api/desktop/ws': (req, server) => upgradeWs(req, server, 'desktop'),
|
'/api/desktop/ws': (req, server) => upgradeWs(req, server, 'desktop'),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @deprecated Legacy chat types - Use types from ./pi/types.ts instead
|
* @deprecated Legacy chat types - Use types from ./chat/types.ts instead
|
||||||
*
|
*
|
||||||
* This file is kept for backward compatibility with existing code.
|
* This file is kept for backward compatibility with existing code.
|
||||||
* New code should import from ./pi/types.ts
|
* New code should import from ./chat/types.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Re-export new Pi types for compatibility
|
// Re-export new Pi types for compatibility
|
||||||
@@ -13,8 +13,8 @@ export type {
|
|||||||
MessageCost,
|
MessageCost,
|
||||||
SessionMeta,
|
SessionMeta,
|
||||||
ModelInfo,
|
ModelInfo,
|
||||||
PiEvent,
|
ChatEvent,
|
||||||
} from './pi/types';
|
} from './chat/types';
|
||||||
|
|
||||||
// Legacy types (kept for compatibility)
|
// Legacy types (kept for compatibility)
|
||||||
export type ImageData = { mediaType: string; data: string };
|
export type ImageData = { mediaType: string; data: string };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
import { createRouter } from '../../create-router';
|
import { createRouter } from '../../create-router';
|
||||||
|
import { getUserSettings } from 'officerdb';
|
||||||
import {
|
import {
|
||||||
getClaudeSessionsCwd,
|
getClaudeSessionsCwd,
|
||||||
listClaudePwds,
|
listClaudePwds,
|
||||||
@@ -8,6 +9,10 @@ import {
|
|||||||
deleteClaudeSession,
|
deleteClaudeSession,
|
||||||
renameClaudeSession,
|
renameClaudeSession,
|
||||||
} from './claude-sessions';
|
} from './claude-sessions';
|
||||||
|
import { listChatModels } from './list-models';
|
||||||
|
import { logger } from './logger';
|
||||||
|
import { readSttConfig } from '../server-settings/stt';
|
||||||
|
import { transcribeAudio } from '../stt/transcribe';
|
||||||
|
|
||||||
export const chatRouter = createRouter();
|
export const chatRouter = createRouter();
|
||||||
|
|
||||||
@@ -52,3 +57,41 @@ chatRouter.patch('/sessions/:id/title', async (ctx) => {
|
|||||||
if (!ok) return ctx.text('Not found', 404);
|
if (!ok) return ctx.text('Not found', 404);
|
||||||
return ctx.json({ ok: true });
|
return ctx.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /chat/models — Claude tiers only (the runner is the `claude` CLI).
|
||||||
|
chatRouter.get('/models', async (ctx: Context) => {
|
||||||
|
try {
|
||||||
|
const models = await listChatModels();
|
||||||
|
const providerNames: Record<string, string> = { 'claude-code': 'Claude Code' };
|
||||||
|
return ctx.json({ models, providerNames, hostHome: process.env.HOME ?? '' });
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Failed to list models', { error: String(err) });
|
||||||
|
return ctx.json({ models: [], providerNames: {} });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /chat/stt — proxy an audio clip to the configured Whisper server.
|
||||||
|
chatRouter.post('/stt', async (ctx: Context) => {
|
||||||
|
const sttConfig = await readSttConfig();
|
||||||
|
if (!sttConfig?.url) {
|
||||||
|
return ctx.json({ error: 'Whisper not configured — set it up in Settings → Speech to Text' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = ctx.get('user');
|
||||||
|
const body = await ctx.req.parseBody();
|
||||||
|
const file = body['file'];
|
||||||
|
if (!file || !(file instanceof File)) {
|
||||||
|
return ctx.json({ error: 'file is required' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = (await getUserSettings(user.id)) as { languages?: { spoken?: string[] } };
|
||||||
|
const spokenLanguages = settings.languages?.spoken ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await transcribeAudio({ file, whisperUrl: sttConfig.url, spokenLanguages });
|
||||||
|
return ctx.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('STT proxy failed', { error: String(err) });
|
||||||
|
return ctx.json({ error: err instanceof Error ? err.message : 'Failed to reach Whisper server' }, 502);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export function invalidateModelCache(): void {
|
|||||||
// No-op: the Claude model list is static. Kept for call-site compatibility.
|
// No-op: the Claude model list is static. Kept for call-site compatibility.
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listPiModels(): Promise<ModelInfo[]> {
|
export async function listChatModels(): Promise<ModelInfo[]> {
|
||||||
return [...CLAUDE_CODE_MODELS];
|
return [...CLAUDE_CODE_MODELS];
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ export type ServerMessage =
|
|||||||
type: 'stopped';
|
type: 'stopped';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PiEvent =
|
export type ChatEvent =
|
||||||
| { type: 'text'; text: string }
|
| { type: 'text'; text: string }
|
||||||
| { type: 'delta'; text: string }
|
| { type: 'delta'; text: string }
|
||||||
| {
|
| {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ServerWebSocket } from 'bun';
|
import type { ServerWebSocket } from 'bun';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import type { ClientMessage, ServerMessage, Message, PiEvent } from './types';
|
import type { ClientMessage, ServerMessage, Message, ChatEvent } from './types';
|
||||||
import { sessionManager } from './session-manager';
|
import { sessionManager } from './session-manager';
|
||||||
import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code';
|
import { sendClaudeCodeStreaming } from '@@/channels/send-claude-code';
|
||||||
import { ensureClaudeSessionsCwd } from '../chat/claude-sessions';
|
import { ensureClaudeSessionsCwd } from './claude-sessions';
|
||||||
import * as sidecar from '@@/sidecar-registry';
|
import * as sidecar from '@@/sidecar-registry';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { getHomeDirForRole } from '../../../servers/data-path';
|
import { getHomeDirForRole } from '../../../servers/data-path';
|
||||||
@@ -113,7 +113,7 @@ export function close(ws: ServerWebSocket<WSData>): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createEventHandler(sessionId: string, model: string, cwd: string) {
|
function createEventHandler(sessionId: string, model: string, cwd: string) {
|
||||||
return async (event: PiEvent): Promise<void> => {
|
return async (event: ChatEvent): Promise<void> => {
|
||||||
const session = sessionManager.getSession(sessionId);
|
const session = sessionManager.getSession(sessionId);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
@@ -441,7 +441,7 @@ async function handleStop(ws: ServerWebSocket<WSData>): Promise<void> {
|
|||||||
sendToClient(ws, { type: 'stopped' });
|
sendToClient(ws, { type: 'stopped' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const piWebsocket = {
|
export const chatWebsocket = {
|
||||||
open,
|
open,
|
||||||
message,
|
message,
|
||||||
close,
|
close,
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import type { Context } from 'hono';
|
|
||||||
import { createRouter } from '../../create-router';
|
|
||||||
import { readSttConfig } from '../server-settings/stt';
|
|
||||||
import { listPiModels } from './list-models';
|
|
||||||
import { logger } from './logger';
|
|
||||||
import { transcribeAudio } from '../stt/transcribe';
|
|
||||||
import { getUserSettings } from 'officerdb';
|
|
||||||
|
|
||||||
export const piRestRouter = createRouter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/pi/models — Claude tiers only.
|
|
||||||
*/
|
|
||||||
piRestRouter.get('/pi/models', async (ctx: Context) => {
|
|
||||||
try {
|
|
||||||
const models = await listPiModels();
|
|
||||||
const providerNames: Record<string, string> = { 'claude-code': 'Claude Code' };
|
|
||||||
return ctx.json({ models, providerNames, hostHome: process.env.HOME ?? '' });
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('Failed to list models', { error: String(err) });
|
|
||||||
return ctx.json({ models: [], providerNames: {} });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /api/pi/stt
|
|
||||||
*/
|
|
||||||
piRestRouter.post('/pi/stt', async (ctx: Context) => {
|
|
||||||
const sttConfig = await readSttConfig();
|
|
||||||
if (!sttConfig?.url) {
|
|
||||||
return ctx.json({ error: 'Whisper not configured — set it up in Settings → Speech to Text' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = ctx.get('user');
|
|
||||||
const body = await ctx.req.parseBody();
|
|
||||||
const file = body['file'];
|
|
||||||
if (!file || !(file instanceof File)) {
|
|
||||||
return ctx.json({ error: 'file is required' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = (await getUserSettings(user.id)) as { languages?: { spoken?: string[] } };
|
|
||||||
const spokenLanguages = settings.languages?.spoken ?? [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await transcribeAudio({ file, whisperUrl: sttConfig.url, spokenLanguages });
|
|
||||||
return ctx.json(result);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('STT proxy failed', { error: String(err) });
|
|
||||||
return ctx.json({ error: err instanceof Error ? err.message : 'Failed to reach Whisper server' }, 502);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { createRouter } from '../../create-router';
|
import { createRouter } from '../../create-router';
|
||||||
import { PI_CONFIG_DIR } from '../../data-path';
|
import { PI_CONFIG_DIR } from '../../data-path';
|
||||||
import { invalidateModelCache } from '../pi/list-models';
|
import { invalidateModelCache } from '../chat/list-models';
|
||||||
import { logger } from '../pi/logger';
|
import { logger } from '../chat/logger';
|
||||||
import { readConfigValue, writeConfigValue } from 'officerdb';
|
import { readConfigValue, writeConfigValue } from 'officerdb';
|
||||||
|
|
||||||
export const piMonoRouter = createRouter();
|
export const piMonoRouter = createRouter();
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { tmpdir } from 'node:os';
|
|||||||
import { getUserSettings } from 'officerdb';
|
import { getUserSettings } from 'officerdb';
|
||||||
import { getTaskByDirName } from './task-files';
|
import { getTaskByDirName } from './task-files';
|
||||||
import { getHomeDirForRole, getHomeDir } from '../../data-path';
|
import { getHomeDirForRole, getHomeDir } from '../../data-path';
|
||||||
import { resolveBaseCwd } from '../pi/websocket';
|
import { resolveBaseCwd } from '../chat/websocket';
|
||||||
import { SANDBOX_HOME } from '../../sidecar/sandbox';
|
import { SANDBOX_HOME } from '../../sidecar/sandbox';
|
||||||
import { sendClaudeCodeStreaming } from '../../channels/send-claude-code';
|
import { sendClaudeCodeStreaming } from '../../channels/send-claude-code';
|
||||||
import type { PiEvent, MessageCost } from '../pi/types';
|
import type { ChatEvent, MessageCost } from '../chat/types';
|
||||||
import * as jobManager from './pipeline-job-manager';
|
import * as jobManager from './pipeline-job-manager';
|
||||||
|
|
||||||
const DEFAULT_MODEL = 'claude-code';
|
const DEFAULT_MODEL = 'claude-code';
|
||||||
@@ -117,7 +117,7 @@ async function runAgenticStep({ userId, email, username, role, taskDirName, prom
|
|||||||
fn();
|
fn();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEvent = (event: PiEvent) => {
|
const onEvent = (event: ChatEvent) => {
|
||||||
if (abortSignal.aborted) return;
|
if (abortSignal.aborted) return;
|
||||||
lastActivity = Date.now();
|
lastActivity = Date.now();
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { findUserByIntegrationConfig, readConfigValue } from 'officerdb';
|
|||||||
import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await';
|
import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await';
|
||||||
import { consumePairingCode } from '../pairing';
|
import { consumePairingCode } from '../pairing';
|
||||||
import { chunkMessage } from './chunker';
|
import { chunkMessage } from './chunker';
|
||||||
import { listPiModels } from '@@/api/pi/list-models';
|
import { listChatModels } from '@@/api/chat/list-models';
|
||||||
import { enqueueJob } from '../../queue/init';
|
import { enqueueJob } from '../../queue/init';
|
||||||
import { readJob } from '@@/queue/storage';
|
import { readJob } from '@@/queue/storage';
|
||||||
import { openUserEmailDb } from '@@/api/email/email-db';
|
import { openUserEmailDb } from '@@/api/email/email-db';
|
||||||
import type { ModelInfo } from '@@/api/pi/types';
|
import type { ModelInfo } from '@@/api/chat/types';
|
||||||
import { toShellUsername } from '@@/data-path';
|
import { toShellUsername } from '@@/data-path';
|
||||||
|
|
||||||
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
||||||
@@ -19,7 +19,7 @@ type AccessPolicy = { allowedModels: string[] };
|
|||||||
const ACCESS_POLICY_KEY = 'pi-access-policy';
|
const ACCESS_POLICY_KEY = 'pi-access-policy';
|
||||||
|
|
||||||
async function getVisibleModels(): Promise<ModelInfo[]> {
|
async function getVisibleModels(): Promise<ModelInfo[]> {
|
||||||
const allModels = await listPiModels();
|
const allModels = await listChatModels();
|
||||||
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
||||||
const allowed = policy.allowedModels;
|
const allowed = policy.allowedModels;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { MessageCost } from '@@/api/pi/types';
|
import type { MessageCost } from '@@/api/chat/types';
|
||||||
import { getUserSettings } from 'officerdb';
|
import { getUserSettings } from 'officerdb';
|
||||||
import { logger } from '@@/api/pi/logger';
|
import { logger } from '@@/api/chat/logger';
|
||||||
import { sendClaudeCode, clearClaudeCodeSession } from './send-claude-code';
|
import { sendClaudeCode, clearClaudeCodeSession } from './send-claude-code';
|
||||||
|
|
||||||
const DEFAULT_MODEL = 'claude-code';
|
const DEFAULT_MODEL = 'claude-code';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { logger } from '@@/api/pi/logger';
|
import { logger } from '@@/api/chat/logger';
|
||||||
import type { MessageCost, PiEvent } from '@@/api/pi/types';
|
import type { MessageCost, ChatEvent } from '@@/api/chat/types';
|
||||||
import * as sidecar from '@@/sidecar-registry';
|
import * as sidecar from '@@/sidecar-registry';
|
||||||
|
|
||||||
type ClaudeCodeParams = {
|
type ClaudeCodeParams = {
|
||||||
@@ -40,7 +40,7 @@ type ClaudeCodeStreamingParams = {
|
|||||||
model?: string;
|
model?: string;
|
||||||
role?: string;
|
role?: string;
|
||||||
resumeSessionId?: string;
|
resumeSessionId?: string;
|
||||||
onEvent: (event: PiEvent) => void;
|
onEvent: (event: ChatEvent) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClaudeCodeStreamingHandle = {
|
type ClaudeCodeStreamingHandle = {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-awai
|
|||||||
import { consumePairingCode } from '../pairing';
|
import { consumePairingCode } from '../pairing';
|
||||||
import { chunkMessage } from './chunker';
|
import { chunkMessage } from './chunker';
|
||||||
import { getTelegramBot } from './bot';
|
import { getTelegramBot } from './bot';
|
||||||
import { listPiModels } from '@@/api/pi/list-models';
|
import { listChatModels } from '@@/api/chat/list-models';
|
||||||
import { enqueueJob } from '../../queue/init';
|
import { enqueueJob } from '../../queue/init';
|
||||||
import { readJob } from '@@/queue/storage';
|
import { readJob } from '@@/queue/storage';
|
||||||
import { openUserEmailDb } from '@@/api/email/email-db';
|
import { openUserEmailDb } from '@@/api/email/email-db';
|
||||||
import type { ModelInfo } from '@@/api/pi/types';
|
import type { ModelInfo } from '@@/api/chat/types';
|
||||||
import { toShellUsername } from '@@/data-path';
|
import { toShellUsername } from '@@/data-path';
|
||||||
|
|
||||||
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
||||||
@@ -20,7 +20,7 @@ type AccessPolicy = { allowedModels: string[] };
|
|||||||
const ACCESS_POLICY_KEY = 'pi-access-policy';
|
const ACCESS_POLICY_KEY = 'pi-access-policy';
|
||||||
|
|
||||||
async function getVisibleModels(): Promise<ModelInfo[]> {
|
async function getVisibleModels(): Promise<ModelInfo[]> {
|
||||||
const allModels = await listPiModels();
|
const allModels = await listChatModels();
|
||||||
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
||||||
const allowed = policy.allowedModels;
|
const allowed = policy.allowedModels;
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { findUserByIntegrationConfig, readConfigValue } from 'officerdb';
|
|||||||
import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await';
|
import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-await';
|
||||||
import { consumePairingCode } from '../pairing';
|
import { consumePairingCode } from '../pairing';
|
||||||
import { getWhatsAppClient } from './bot';
|
import { getWhatsAppClient } from './bot';
|
||||||
import { listPiModels } from '@@/api/pi/list-models';
|
import { listChatModels } from '@@/api/chat/list-models';
|
||||||
import { enqueueJob } from '../../queue/init';
|
import { enqueueJob } from '../../queue/init';
|
||||||
import { readJob } from '@@/queue/storage';
|
import { readJob } from '@@/queue/storage';
|
||||||
import { openUserEmailDb } from '@@/api/email/email-db';
|
import { openUserEmailDb } from '@@/api/email/email-db';
|
||||||
import type { ModelInfo } from '@@/api/pi/types';
|
import type { ModelInfo } from '@@/api/chat/types';
|
||||||
import { toShellUsername } from '@@/data-path';
|
import { toShellUsername } from '@@/data-path';
|
||||||
|
|
||||||
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
const PAIRING_CODE_PATTERN = /^[A-Z0-9]{6}$/;
|
||||||
@@ -24,7 +24,7 @@ function extractPhone(waId: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getVisibleModels(): Promise<ModelInfo[]> {
|
async function getVisibleModels(): Promise<ModelInfo[]> {
|
||||||
const allModels = await listPiModels();
|
const allModels = await listChatModels();
|
||||||
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
const policy = await readConfigValue<AccessPolicy>(ACCESS_POLICY_KEY, { allowedModels: [] });
|
||||||
const allowed = policy.allowedModels;
|
const allowed = policy.allowedModels;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { settingsRouter } from './api/settings/settings';
|
|||||||
import { dashboardsRouter } from './api/dashboards';
|
import { dashboardsRouter } from './api/dashboards';
|
||||||
import { taskLogsRouter } from './api/task-logs/task-logs';
|
import { taskLogsRouter } from './api/task-logs/task-logs';
|
||||||
import { router as fileBrowserRouter } from './api/file-browser/router';
|
import { router as fileBrowserRouter } from './api/file-browser/router';
|
||||||
import { piRestRouter } from './api/pi/rest';
|
|
||||||
import { devServerRouter, devServerProxyRouter } from './api/dev-server/router';
|
import { devServerRouter, devServerProxyRouter } from './api/dev-server/router';
|
||||||
import { dockRouter } from './api/dock/dock';
|
import { dockRouter } from './api/dock/dock';
|
||||||
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations';
|
||||||
@@ -107,7 +106,6 @@ protectedRouter.route('/pipeline-jobs', pipelineJobsRouter);
|
|||||||
protectedRouter.route('/jobs', pipelineJobsRouter); // unified jobs API (script + pipeline); /pipeline-jobs kept for the existing UI
|
protectedRouter.route('/jobs', pipelineJobsRouter); // unified jobs API (script + pipeline); /pipeline-jobs kept for the existing UI
|
||||||
desktopRouter.use(superAdminMiddleware);
|
desktopRouter.use(superAdminMiddleware);
|
||||||
protectedRouter.route('/desktop', desktopRouter);
|
protectedRouter.route('/desktop', desktopRouter);
|
||||||
protectedRouter.route('/', piRestRouter);
|
|
||||||
|
|
||||||
honoServer.route('/api', protectedRouter);
|
honoServer.route('/api', protectedRouter);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type {
|
|||||||
VncSessionInfo,
|
VncSessionInfo,
|
||||||
} from './sidecar/protocol';
|
} from './sidecar/protocol';
|
||||||
import type { SidecarRegistration } from './sidecar/registration-protocol';
|
import type { SidecarRegistration } from './sidecar/registration-protocol';
|
||||||
import type { PiEvent } from './api/pi/types';
|
import type { ChatEvent } from './api/chat/types';
|
||||||
|
|
||||||
// ── Types ──
|
// ── Types ──
|
||||||
|
|
||||||
@@ -339,7 +339,7 @@ export function clearClaudeSession(sessionKey: string, email?: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onClaudeEvent(handler: (sessionKey: string, event: PiEvent) => void): () => void {
|
export function onClaudeEvent(handler: (sessionKey: string, event: ChatEvent) => void): () => void {
|
||||||
return on('claude:event', (msg) => {
|
return on('claude:event', (msg) => {
|
||||||
if (msg.type === 'claude:event') {
|
if (msg.type === 'claude:event') {
|
||||||
handler(
|
handler(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import type { Subprocess } from 'bun';
|
import type { Subprocess } from 'bun';
|
||||||
import type { PiEvent } from '../../api/pi/types';
|
import type { ChatEvent } from '../../api/chat/types';
|
||||||
import type { ClaudeSpawnParams, ClaudeSpawnStreamingParams, ClaudeCodeResult } from '../protocol';
|
import type { ClaudeSpawnParams, ClaudeSpawnStreamingParams, ClaudeCodeResult } from '../protocol';
|
||||||
import { buildSandboxPrefix, buildRunuserSuffix, SANDBOX_HOME } from '../sandbox';
|
import { buildSandboxPrefix, buildRunuserSuffix, SANDBOX_HOME } from '../sandbox';
|
||||||
import { setClaudeSession, clearClaudeSession, getClaudeSession } from './state';
|
import { setClaudeSession, clearClaudeSession, getClaudeSession } from './state';
|
||||||
@@ -145,7 +145,7 @@ export async function spawnClaude(params: ClaudeSpawnParams): Promise<ClaudeCode
|
|||||||
|
|
||||||
export async function spawnClaudeStreaming(
|
export async function spawnClaudeStreaming(
|
||||||
params: ClaudeSpawnStreamingParams,
|
params: ClaudeSpawnStreamingParams,
|
||||||
onEvent: (event: PiEvent) => void,
|
onEvent: (event: ChatEvent) => void,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { prompt, sessionKey, email } = params;
|
const { prompt, sessionKey, email } = params;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { describe, test, expect } from 'bun:test';
|
import { describe, test, expect } from 'bun:test';
|
||||||
import { processLine, createParseState, parseStream } from './stream-parser';
|
import { processLine, createParseState, parseStream } from './stream-parser';
|
||||||
import type { PiEvent } from '../../api/pi/types';
|
import type { ChatEvent } from '../../api/chat/types';
|
||||||
import type { StreamParserCallbacks, ParseState } from './stream-parser';
|
import type { StreamParserCallbacks, ParseState } from './stream-parser';
|
||||||
|
|
||||||
function makeCallbacks(): { events: PiEvent[]; sessionIds: string[]; callbacks: StreamParserCallbacks } {
|
function makeCallbacks(): { events: ChatEvent[]; sessionIds: string[]; callbacks: StreamParserCallbacks } {
|
||||||
const events: PiEvent[] = [];
|
const events: ChatEvent[] = [];
|
||||||
const sessionIds: string[] = [];
|
const sessionIds: string[] = [];
|
||||||
return {
|
return {
|
||||||
events,
|
events,
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
* system init, and result messages.
|
* system init, and result messages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PiEvent, MessageCost } from '../../api/pi/types';
|
import type { ChatEvent, MessageCost } from '../../api/chat/types';
|
||||||
|
|
||||||
type SessionCallback = (sessionId: string) => void;
|
type SessionCallback = (sessionId: string) => void;
|
||||||
|
|
||||||
type StreamParserCallbacks = {
|
type StreamParserCallbacks = {
|
||||||
onEvent: (event: PiEvent) => void;
|
onEvent: (event: ChatEvent) => void;
|
||||||
onSessionId: SessionCallback;
|
onSessionId: SessionCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ type ParseState = {
|
|||||||
gotResult: boolean;
|
gotResult: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function flushTextBuffer(state: ParseState, onEvent: (event: PiEvent) => void): void {
|
function flushTextBuffer(state: ParseState, onEvent: (event: ChatEvent) => void): void {
|
||||||
if (state.textBuffer) {
|
if (state.textBuffer) {
|
||||||
onEvent({ type: 'text', text: state.textBuffer });
|
onEvent({ type: 'text', text: state.textBuffer });
|
||||||
state.textBuffer = '';
|
state.textBuffer = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStreamEvent(msg: Record<string, unknown>, state: ParseState, onEvent: (event: PiEvent) => void): void {
|
function handleStreamEvent(msg: Record<string, unknown>, state: ParseState, onEvent: (event: ChatEvent) => void): void {
|
||||||
const event = msg.event as Record<string, unknown> | undefined;
|
const event = msg.event as Record<string, unknown> | undefined;
|
||||||
if (event?.type === 'content_block_delta') {
|
if (event?.type === 'content_block_delta') {
|
||||||
const delta = event.delta as Record<string, unknown> | undefined;
|
const delta = event.delta as Record<string, unknown> | undefined;
|
||||||
@@ -38,7 +38,7 @@ function handleStreamEvent(msg: Record<string, unknown>, state: ParseState, onEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAssistant(msg: Record<string, unknown>, state: ParseState, onEvent: (event: PiEvent) => void): void {
|
function handleAssistant(msg: Record<string, unknown>, state: ParseState, onEvent: (event: ChatEvent) => void): void {
|
||||||
const message = msg.message as Record<string, unknown> | undefined;
|
const message = msg.message as Record<string, unknown> | undefined;
|
||||||
const content = message?.content as Array<Record<string, unknown>> | undefined;
|
const content = message?.content as Array<Record<string, unknown>> | undefined;
|
||||||
if (!Array.isArray(content)) return;
|
if (!Array.isArray(content)) return;
|
||||||
@@ -59,7 +59,7 @@ function handleAssistant(msg: Record<string, unknown>, state: ParseState, onEven
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUser(msg: Record<string, unknown>, onEvent: (event: PiEvent) => void): void {
|
function handleUser(msg: Record<string, unknown>, onEvent: (event: ChatEvent) => void): void {
|
||||||
const message = msg.message as Record<string, unknown> | undefined;
|
const message = msg.message as Record<string, unknown> | undefined;
|
||||||
const content = message?.content as Array<Record<string, unknown>> | undefined;
|
const content = message?.content as Array<Record<string, unknown>> | undefined;
|
||||||
if (!Array.isArray(content)) return;
|
if (!Array.isArray(content)) return;
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
|
|||||||
case 'claude:spawn-streaming': {
|
case 'claude:spawn-streaming': {
|
||||||
reply({ type: 'claude:spawned', id: cmd.id, sessionKey: cmd.params.sessionKey });
|
reply({ type: 'claude:spawned', id: cmd.id, sessionKey: cmd.params.sessionKey });
|
||||||
|
|
||||||
const onEvent = (event: import('../../api/pi/types').PiEvent) => {
|
const onEvent = (event: import('../../api/chat/types').ChatEvent) => {
|
||||||
connection.send({ type: 'claude:event', sessionKey: cmd.params.sessionKey, event });
|
connection.send({ type: 'claude:event', sessionKey: cmd.params.sessionKey, event });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MessageCost, PiEvent } from '../api/pi/types';
|
import type { MessageCost, ChatEvent } from '../api/chat/types';
|
||||||
|
|
||||||
// ── Envelope ──
|
// ── Envelope ──
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export type SidecarEvent =
|
|||||||
| { type: 'proxy:secret'; id: string; secret: string }
|
| { type: 'proxy:secret'; id: string; secret: string }
|
||||||
// Claude Code
|
// Claude Code
|
||||||
| { type: 'claude:spawned'; id: string; sessionKey: string }
|
| { type: 'claude:spawned'; id: string; sessionKey: string }
|
||||||
| { type: 'claude:event'; sessionKey: string; event: PiEvent }
|
| { type: 'claude:event'; sessionKey: string; event: ChatEvent }
|
||||||
| { type: 'claude:result'; id: string; result: ClaudeCodeResult }
|
| { type: 'claude:result'; id: string; result: ClaudeCodeResult }
|
||||||
| { type: 'claude:error'; id: string; error: string }
|
| { type: 'claude:error'; id: string; error: string }
|
||||||
| { type: 'claude:killed'; id: string }
|
| { type: 'claude:killed'; id: string }
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function useAttachments(params?: UseAttachmentsParams) {
|
|||||||
const res = await client.post<{ url: string; title: string; content: string; attachmentId: string }>('/scrape', {
|
const res = await client.post<{ url: string; title: string; content: string; attachmentId: string }>('/scrape', {
|
||||||
url,
|
url,
|
||||||
...(params?.sessionId ? { sessionId: params.sessionId } : {}),
|
...(params?.sessionId ? { sessionId: params.sessionId } : {}),
|
||||||
provider: 'pi-mono',
|
provider: 'chat',
|
||||||
});
|
});
|
||||||
setAttachments((prev) =>
|
setAttachments((prev) =>
|
||||||
prev.map((a, i) =>
|
prev.map((a, i) =>
|
||||||
@@ -48,7 +48,7 @@ export function useAttachments(params?: UseAttachmentsParams) {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
if (params?.sessionId) formData.append('sessionId', params.sessionId);
|
if (params?.sessionId) formData.append('sessionId', params.sessionId);
|
||||||
formData.append('provider', 'pi-mono');
|
formData.append('provider', 'chat');
|
||||||
|
|
||||||
const res = await client.post<{ filename: string; dataUrl: string; attachmentId: string }>('/upload', formData);
|
const res = await client.post<{ filename: string; dataUrl: string; attachmentId: string }>('/upload', formData);
|
||||||
setAttachments((prev) =>
|
setAttachments((prev) =>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function useAudioRecording(onTranscription: (text: string) => void) {
|
|||||||
formData.append('response_format', 'json');
|
formData.append('response_format', 'json');
|
||||||
|
|
||||||
const token = localStorage.getItem('BEARER_TOKEN') ?? sessionStorage.getItem('BEARER_TOKEN');
|
const token = localStorage.getItem('BEARER_TOKEN') ?? sessionStorage.getItem('BEARER_TOKEN');
|
||||||
const res = await fetch('/api/pi/stt', {
|
const res = await fetch('/api/chat/stt', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
|||||||
|
|
||||||
const token = localStorage.getItem('BEARER_TOKEN');
|
const token = localStorage.getItem('BEARER_TOKEN');
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
const wsUrl = `${protocol}//${window.location.host}/api/pi/chat/ws?token=${token}`;
|
const wsUrl = `${protocol}//${window.location.host}/api/chat/chat/ws?token=${token}`;
|
||||||
|
|
||||||
function flushStreaming() {
|
function flushStreaming() {
|
||||||
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export { useSettings, DEFAULT_SETTINGS } from './useSettings';
|
|||||||
export type { UseSettingsType, UserSettings, UserState } from './useSettings';
|
export type { UseSettingsType, UserSettings, UserState } from './useSettings';
|
||||||
export { useUserState } from './useUserState';
|
export { useUserState } from './useUserState';
|
||||||
export { useDashboardState } from './useDashboardState';
|
export { useDashboardState } from './useDashboardState';
|
||||||
export { usePiModels, useVisiblePiModels, useUserVisibleModels, useEnabledPiModels, modelKey } from './useModels';
|
export { useModels, useVisibleModels, useUserVisibleModels, useEnabledModels, modelKey } from './useModels';
|
||||||
export type { ModelOption } from './useModels';
|
export type { ModelOption } from './useModels';
|
||||||
export { useAccessPolicy } from './useAccessPolicy';
|
export { useAccessPolicy } from './useAccessPolicy';
|
||||||
export { useClaudeSessions, useChatPwds } from './useClaudeSessions';
|
export { useClaudeSessions, useChatPwds } from './useClaudeSessions';
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ export function getHostHome(): string {
|
|||||||
return globalHostHome;
|
return globalHostHome;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePiModels() {
|
export function useModels() {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
const { data: models = [] } = useQuery<ModelOption[]>({
|
const { data: models = [] } = useQuery<ModelOption[]>({
|
||||||
queryKey: ['PI_MODELS'],
|
queryKey: ['CHAT_MODELS'],
|
||||||
enabled: isAuthenticated,
|
enabled: isAuthenticated,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await client.get<{
|
const data = await client.get<{
|
||||||
models: ModelOption[];
|
models: ModelOption[];
|
||||||
providerNames?: Record<string, string>;
|
providerNames?: Record<string, string>;
|
||||||
hostHome?: string;
|
hostHome?: string;
|
||||||
}>('/pi/models');
|
}>('/chat/models');
|
||||||
|
|
||||||
if (data.providerNames) {
|
if (data.providerNames) {
|
||||||
globalProviderNames = data.providerNames;
|
globalProviderNames = data.providerNames;
|
||||||
@@ -54,8 +54,8 @@ export function usePiModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Filter models by system-wide access policy. New providers pass through. */
|
/** Filter models by system-wide access policy. New providers pass through. */
|
||||||
export function useVisiblePiModels() {
|
export function useVisibleModels() {
|
||||||
const models = usePiModels();
|
const models = useModels();
|
||||||
const { policy } = useAccessPolicy();
|
const { policy } = useAccessPolicy();
|
||||||
const allowed = policy.allowedModels;
|
const allowed = policy.allowedModels;
|
||||||
|
|
||||||
@@ -74,8 +74,8 @@ export function useVisiblePiModels() {
|
|||||||
|
|
||||||
/** Models visible to the current user: system policy (members) or all (admins), minus per-user hidden. */
|
/** Models visible to the current user: system policy (members) or all (admins), minus per-user hidden. */
|
||||||
export function useUserVisibleModels() {
|
export function useUserVisibleModels() {
|
||||||
const allModels = usePiModels();
|
const allModels = useModels();
|
||||||
const policyModels = useVisiblePiModels();
|
const policyModels = useVisibleModels();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ export function useUserVisibleModels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Strict filtering — only explicitly allowed models, no "new provider" passthrough. */
|
/** Strict filtering — only explicitly allowed models, no "new provider" passthrough. */
|
||||||
export function useEnabledPiModels() {
|
export function useEnabledModels() {
|
||||||
const models = usePiModels();
|
const models = useModels();
|
||||||
const { policy } = useAccessPolicy();
|
const { policy } = useAccessPolicy();
|
||||||
const allowed = policy.allowedModels;
|
const allowed = policy.allowedModels;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user