Release v1.01 Enhanced: Vi Control, TUI Gen5, Core Stability
This commit is contained in:
232
bin/ui/models/PartModel.mjs
Normal file
232
bin/ui/models/PartModel.mjs
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Part Model - Structured message parts
|
||||
*
|
||||
* Based on sst/opencode message architecture
|
||||
* Credit: https://github.com/sst/opencode
|
||||
*
|
||||
* Normalizes all content into typed parts for consistent rendering
|
||||
*/
|
||||
|
||||
// Part types enum
|
||||
export const PART_TYPES = {
|
||||
TEXT: 'text', // User/assistant prose
|
||||
REASONING: 'reasoning', // Thinking/intent trace
|
||||
TOOL_CALL: 'tool_call', // Tool invocation
|
||||
TOOL_RESULT: 'tool_result', // Tool output
|
||||
FILE_SNIPPET: 'file_snippet', // Code file content
|
||||
DIFF: 'diff', // Diff view
|
||||
TODO: 'todo', // Todo/checklist
|
||||
SYSTEM: 'system', // System message
|
||||
ERROR: 'error' // Error message
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a text part (user/assistant prose)
|
||||
*/
|
||||
export function createTextPart(content, role = 'assistant') {
|
||||
return {
|
||||
type: PART_TYPES.TEXT,
|
||||
role,
|
||||
content,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reasoning part (thinking/intent trace)
|
||||
*/
|
||||
export function createReasoningPart(intent, next, why = null, steps = []) {
|
||||
return {
|
||||
type: PART_TYPES.REASONING,
|
||||
intent,
|
||||
next,
|
||||
why,
|
||||
steps,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool call part
|
||||
*/
|
||||
export function createToolCallPart(toolName, args = {}, status = 'running') {
|
||||
return {
|
||||
type: PART_TYPES.TOOL_CALL,
|
||||
toolName,
|
||||
args,
|
||||
status, // 'running' | 'done' | 'failed'
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool result part
|
||||
*/
|
||||
export function createToolResultPart(toolName, output, summary = null, exitCode = 0) {
|
||||
return {
|
||||
type: PART_TYPES.TOOL_RESULT,
|
||||
toolName,
|
||||
output,
|
||||
summary,
|
||||
exitCode,
|
||||
isError: exitCode !== 0,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file snippet part
|
||||
*/
|
||||
export function createFileSnippetPart(filename, content, language = null, startLine = 1) {
|
||||
const lines = content.split('\n').length;
|
||||
return {
|
||||
type: PART_TYPES.FILE_SNIPPET,
|
||||
filename,
|
||||
content,
|
||||
language: language || detectLanguage(filename),
|
||||
startLine,
|
||||
lineCount: lines,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a diff part
|
||||
*/
|
||||
export function createDiffPart(filename, diff, mode = 'unified') {
|
||||
return {
|
||||
type: PART_TYPES.DIFF,
|
||||
filename,
|
||||
diff,
|
||||
mode, // 'unified' | 'split'
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a todo part
|
||||
*/
|
||||
export function createTodoPart(items) {
|
||||
return {
|
||||
type: PART_TYPES.TODO,
|
||||
items: items.map(item => ({
|
||||
text: item.text,
|
||||
status: item.status || 'pending', // 'pending' | 'in_progress' | 'done'
|
||||
checked: item.status === 'done'
|
||||
})),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a system part
|
||||
*/
|
||||
export function createSystemPart(content, subtype = 'info') {
|
||||
return {
|
||||
type: PART_TYPES.SYSTEM,
|
||||
content,
|
||||
subtype, // 'info' | 'warning' | 'success'
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error part
|
||||
*/
|
||||
export function createErrorPart(message, details = null, stack = null) {
|
||||
return {
|
||||
type: PART_TYPES.ERROR,
|
||||
message,
|
||||
details,
|
||||
stack,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect language from filename
|
||||
*/
|
||||
function detectLanguage(filename) {
|
||||
if (!filename) return null;
|
||||
const ext = filename.split('.').pop()?.toLowerCase();
|
||||
const langMap = {
|
||||
js: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
||||
ts: 'typescript', tsx: 'typescript',
|
||||
py: 'python',
|
||||
rb: 'ruby',
|
||||
go: 'go',
|
||||
rs: 'rust',
|
||||
java: 'java',
|
||||
c: 'c', cpp: 'cpp', h: 'c',
|
||||
cs: 'csharp',
|
||||
php: 'php',
|
||||
sh: 'bash', bash: 'bash',
|
||||
ps1: 'powershell',
|
||||
json: 'json',
|
||||
yaml: 'yaml', yml: 'yaml',
|
||||
md: 'markdown',
|
||||
html: 'html',
|
||||
css: 'css',
|
||||
sql: 'sql'
|
||||
};
|
||||
return langMap[ext] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw message content into parts
|
||||
* Extracts code blocks, tool calls, etc.
|
||||
*/
|
||||
export function parseContentToParts(content, role = 'assistant') {
|
||||
const parts = [];
|
||||
|
||||
// Simple parsing - extract code blocks
|
||||
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
|
||||
while ((match = codeBlockRegex.exec(content)) !== null) {
|
||||
// Text before code block
|
||||
if (match.index > lastIndex) {
|
||||
const text = content.slice(lastIndex, match.index).trim();
|
||||
if (text) {
|
||||
parts.push(createTextPart(text, role));
|
||||
}
|
||||
}
|
||||
|
||||
// Code block
|
||||
const lang = match[1] || null;
|
||||
const code = match[2];
|
||||
parts.push(createFileSnippetPart(null, code, lang));
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
// Remaining text after last code block
|
||||
if (lastIndex < content.length) {
|
||||
const text = content.slice(lastIndex).trim();
|
||||
if (text) {
|
||||
parts.push(createTextPart(text, role));
|
||||
}
|
||||
}
|
||||
|
||||
// If no parts created, treat entire content as text
|
||||
if (parts.length === 0) {
|
||||
parts.push(createTextPart(content, role));
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
export default {
|
||||
PART_TYPES,
|
||||
createTextPart,
|
||||
createReasoningPart,
|
||||
createToolCallPart,
|
||||
createToolResultPart,
|
||||
createFileSnippetPart,
|
||||
createDiffPart,
|
||||
createTodoPart,
|
||||
createSystemPart,
|
||||
createErrorPart,
|
||||
parseContentToParts
|
||||
};
|
||||
Reference in New Issue
Block a user