initial commit -- llama-cli GUI example
This commit is contained in:
commit
3d72105c2c
4 changed files with 121 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
bun.lock
|
2
entry/run-llamacli-gui.js
Normal file
2
entry/run-llamacli-gui.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import { run_process_gui } from '../templates/run-process-gui'
|
||||
run_process_gui({ hostname: '0.0.0.0', page_title: 'framerock examples | Run llama-cli GUI' }, '/home/user/llama.cpp/build/bin', [ './llama-cli', '-m', '/home/user/models/LFM2-350M-Q4_K_M.gguf', '-p', 'I believe the meaning of life is', '-no-cnv' ])
|
5
package.json
Normal file
5
package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"framerock": "git+https://git.daemons.my/dab/framerock.git"
|
||||
}
|
||||
}
|
112
templates/run-process-gui.js
Normal file
112
templates/run-process-gui.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { async_run } from 'framerock'
|
||||
const run_process_gui = (runtime_conf, cwd_for_spawn, args_for_spawn) => {
|
||||
const jsbuild_app_frontend = async function () {
|
||||
return `
|
||||
const streams = {}
|
||||
const elems_terminals = {}
|
||||
const async_process_stream = async function (stream_id, stream_key, callback) {
|
||||
for await (let chunk of streams[stream_id][stream_key].readable) {
|
||||
callback(chunk)
|
||||
}
|
||||
return
|
||||
}
|
||||
const setup_dom = function () {
|
||||
document.body.style = 'height: 100%;'
|
||||
document.head.insertAdjacentHTML('beforeend', '<style type="text/css">html, body{ height: 100%; width: 100%; margin: 0px; padding: 0px;}</style>')
|
||||
const elem_root = document.createElement('div')
|
||||
elem_root.style = 'height: 100%; width: 100%; display: flex; font-size: 16px;'
|
||||
for (let [ stream_key, style ] of [
|
||||
['stderr', { backgroundColor: '#333' }],
|
||||
['stdout', { backgroundColor: '#000' }]
|
||||
]) {
|
||||
const elem_terminal = document.createElement('div')
|
||||
elems_terminals[stream_key] = elem_terminal
|
||||
Object.assign(elem_terminal.style, {
|
||||
flex: '1', fontFamily: 'monospace', color: '#FFF', height: '100%', overflow: 'auto', padding: '20px', boxSizing: 'border-box', whiteSpace: 'pre',
|
||||
...style
|
||||
})
|
||||
elem_root.appendChild(elem_terminal)
|
||||
}
|
||||
document.body.appendChild(elem_root)
|
||||
return
|
||||
}
|
||||
|
||||
const on_open = function () {
|
||||
const main_streamid = '1'
|
||||
streams[main_streamid] = {
|
||||
prev_message_idx: -1,
|
||||
stdout: new TextDecoderStream(),
|
||||
stderr: new TextDecoderStream(),
|
||||
}
|
||||
for (let stream_key of ['stdout', 'stderr']) {
|
||||
async_process_stream(main_streamid, stream_key, (chunk) => {elems_terminals[stream_key].textContent += chunk;}).then(()=>{}).catch(console.error)
|
||||
}
|
||||
FRAMEROCK_UTILS.transport_send_bytes(new TextEncoder().encode(JSON.stringify([ 'SPAWN_PROCESS_INIT', { stream_id: main_streamid } ])))
|
||||
console.info(['Process Spawned'])
|
||||
return
|
||||
}
|
||||
const on_message = function (event) {
|
||||
const [ evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(event.data))
|
||||
if (evt_type === 'SPAWN_PROCESS_EVENT') {
|
||||
const [ stream_id, msg_idx, payload ] = evt_data
|
||||
const stream = streams[stream_id]
|
||||
if (stream === undefined) {
|
||||
console.error(['Unexpected: missing ref to stream:', stream_id])
|
||||
return
|
||||
}
|
||||
if (msg_idx !== stream.prev_message_idx+1) {
|
||||
console.error('received message out of order, skipping')
|
||||
return
|
||||
}
|
||||
stream.prev_message_idx = msg_idx*1
|
||||
const [ evt_subtype, evt_subdata ] = payload
|
||||
if (evt_subtype === 'stream_chunk') {
|
||||
const [ stream_key, stream_data ] = evt_subdata
|
||||
if (stream_data === null) {
|
||||
stream[stream_key].writable.close()
|
||||
}
|
||||
else {
|
||||
const writer = stream[stream_key].writable.getWriter()
|
||||
writer.write(new Uint8Array(stream_data))
|
||||
writer.releaseLock()
|
||||
}
|
||||
}
|
||||
else if (evt_subtype === 'exited') {
|
||||
console.info(['Process Exited', evt_subdata])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
setup_dom()
|
||||
FRAMEROCK_UTILS.setup_transport({ on_open, on_message })
|
||||
`.trim()
|
||||
}
|
||||
const handle_transport_bytes = function (utils, message) {
|
||||
const [ evt_type, evt_data ] = JSON.parse(new TextDecoder().decode(message))
|
||||
if (evt_type === 'SPAWN_PROCESS_INIT') {
|
||||
let msg_idx = 0
|
||||
const wrap_send = (payload) => {
|
||||
utils.transport_send_bytes(new TextEncoder().encode(JSON.stringify([ 'SPAWN_PROCESS_EVENT', [ evt_data.stream_id, msg_idx*1, payload ] ])))
|
||||
msg_idx++
|
||||
return
|
||||
}
|
||||
const onExit = (p, exitCode, signalCode, error) => {
|
||||
wrap_send(['exited', { exitCode, signalCode }])
|
||||
return
|
||||
}
|
||||
const proc = Bun.spawn(args_for_spawn, { cwd: cwd_for_spawn, stdout: 'pipe', stderr: 'pipe', onExit })
|
||||
const async_handle_chunks = async (stream_key) => {
|
||||
for await (const chunk of proc[stream_key]) {
|
||||
wrap_send(['stream_chunk', [ stream_key, [ ...chunk ] ]])
|
||||
}
|
||||
wrap_send(['stream_chunk', [ stream_key, null ]])
|
||||
return
|
||||
}
|
||||
Promise.all([ async_handle_chunks('stdout'), async_handle_chunks('stderr') ]).then(()=>{}).catch(console.error)
|
||||
}
|
||||
return
|
||||
}
|
||||
async_run({ config: { ...runtime_conf }, jsbuild_app_frontend, handle_transport_bytes }).then(()=>{}).catch(console.error)
|
||||
return
|
||||
}
|
||||
export { run_process_gui }
|
Loading…
Add table
Reference in a new issue