feat: add HTTP API endpoints for headless mode
This commit is contained in:
@@ -59,10 +59,48 @@ function getIndexPath() {
|
||||
}
|
||||
|
||||
function startHttpServer(port) {
|
||||
const distPath = app.isPackaged
|
||||
const distPath = app.isPackaged
|
||||
? path.join(__dirname, '..', 'dist')
|
||||
: path.join(__dirname, '..', 'dist');
|
||||
|
||||
|
||||
// Reusable function for listing docs files
|
||||
function listDocsFilesHandler(basePath) {
|
||||
const docsPath = path.join(basePath, 'api');
|
||||
if (!fs.existsSync(docsPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
||||
function walkDir(dir, baseDir) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walkDir(fullPath, baseDir);
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
const relativePath = path.relative(baseDir, fullPath);
|
||||
result.push({
|
||||
name: entry.name.replace('.md', ''),
|
||||
path: fullPath,
|
||||
relativePath: relativePath.replace(/\\/g, '/')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walkDir(docsPath, docsPath);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reusable function for reading doc file
|
||||
function readDocFileHandler(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
return fs.readFileSync(filePath, 'utf-8');
|
||||
}
|
||||
|
||||
const mimeTypes = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'application/javascript',
|
||||
@@ -81,8 +119,47 @@ function startHttpServer(port) {
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
// Handle API endpoints
|
||||
const urlObj = new URL(req.url, `http://localhost:${port}`);
|
||||
if (urlObj.pathname === '/api/list-docs-files') {
|
||||
const basePath = urlObj.searchParams.get('basePath');
|
||||
if (!basePath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'basePath is required' }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const files = listDocsFilesHandler(basePath);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(files));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlObj.pathname === '/api/read-doc-file') {
|
||||
const filePathParam = urlObj.searchParams.get('path');
|
||||
if (!filePathParam) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'path is required' }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const content = readDocFileHandler(filePathParam);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ content }));
|
||||
} catch (err) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve static files
|
||||
let filePath = path.join(distPath, req.url === '/' ? 'index.html' : req.url);
|
||||
|
||||
|
||||
const ext = path.extname(filePath);
|
||||
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
||||
|
||||
|
||||
@@ -88,18 +88,25 @@ export const ApiDocViewer = ({ onDocsPathChange, showAddModal, onCloseAddModal }
|
||||
setErrorMsg('请输入文档路径')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!window.electronAPI) {
|
||||
setErrorMsg('Electron API 不可用,请使用打包后的应用')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
setIsLoading(true)
|
||||
setErrorMsg(null)
|
||||
|
||||
|
||||
try {
|
||||
const files = await window.electronAPI.listDocsFiles(basePath)
|
||||
|
||||
let files: { name: string; path: string; relativePath: string }[] = []
|
||||
|
||||
if (window.electronAPI) {
|
||||
files = await window.electronAPI.listDocsFiles(basePath)
|
||||
} else {
|
||||
// Use HTTP API in headless mode
|
||||
const response = await fetch(`/api/list-docs-files?basePath=${encodeURIComponent(basePath)}`)
|
||||
if (!response.ok) {
|
||||
const err = await response.json()
|
||||
throw new Error(err.error || 'Failed to list docs files')
|
||||
}
|
||||
files = await response.json()
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
setErrorMsg(`路径 "${basePath}/api" 下没有找到 .md 文件`)
|
||||
setExternalDocs([])
|
||||
@@ -108,10 +115,22 @@ export const ApiDocViewer = ({ onDocsPathChange, showAddModal, onCloseAddModal }
|
||||
setCurrentContent('')
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
const docs: ExternalDoc[] = []
|
||||
for (const file of files) {
|
||||
const content = await window.electronAPI.readDocFile(file.path)
|
||||
let content: string | null = null
|
||||
|
||||
if (window.electronAPI) {
|
||||
content = await window.electronAPI.readDocFile(file.path)
|
||||
} else {
|
||||
// Use HTTP API in headless mode
|
||||
const response = await fetch(`/api/read-doc-file?path=${encodeURIComponent(file.path)}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
content = data.content
|
||||
}
|
||||
}
|
||||
|
||||
if (content) {
|
||||
docs.push({ name: file.name, path: file.path, relativePath: file.relativePath.replace(/^api\//, ''), content })
|
||||
}
|
||||
|
||||
@@ -12,10 +12,22 @@ export default function BlueprintPage({ docsPath }: BlueprintPageProps) {
|
||||
const setBlueprintData = useBlueprintStore(state => state.setBlueprintData);
|
||||
|
||||
const loadBlueprint = useCallback(async () => {
|
||||
if (!docsPath || !window.electronAPI) return;
|
||||
|
||||
if (!docsPath) return;
|
||||
|
||||
const blueprintPath = docsPath.replace(/\\/g, '/') + '/blueprint.md';
|
||||
const content = await window.electronAPI.readDocFile(blueprintPath);
|
||||
let content: string | null = null;
|
||||
|
||||
if (window.electronAPI) {
|
||||
content = await window.electronAPI.readDocFile(blueprintPath);
|
||||
} else {
|
||||
// Use HTTP API in headless mode
|
||||
const response = await fetch(`/api/read-doc-file?path=${encodeURIComponent(blueprintPath)}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
content = data.content;
|
||||
}
|
||||
}
|
||||
|
||||
if (content) {
|
||||
const data = parseBlueprintFromMd(content);
|
||||
setBlueprintData(data);
|
||||
|
||||
Reference in New Issue
Block a user