Add service orchestration and web UI
This commit is contained in:
6
internal/service/templates/static/body.gohtml
Normal file
6
internal/service/templates/static/body.gohtml
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "body" }}
|
||||
<body>
|
||||
{{ template "main" . }}
|
||||
{{ template "templates" . }}
|
||||
</body>
|
||||
{{ end }}
|
||||
41
internal/service/templates/static/footer/compose.gohtml
Normal file
41
internal/service/templates/static/footer/compose.gohtml
Normal file
@@ -0,0 +1,41 @@
|
||||
{{ define "footer/compose" }}
|
||||
<div class="compose">
|
||||
<textarea
|
||||
class="compose__textarea"
|
||||
id="text-input"
|
||||
placeholder="Type a message..."
|
||||
aria-label="Message input"
|
||||
rows="1"
|
||||
></textarea>
|
||||
<div class="compose__attachments" id="attachments"></div>
|
||||
<div class="compose__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="compose__action-btn"
|
||||
id="attach"
|
||||
aria-label="Attach files"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#attach"></use></svg>
|
||||
</button>
|
||||
<input type="file" id="file-input" multiple hidden />
|
||||
<div class="compose__actions-right">
|
||||
<button
|
||||
type="button"
|
||||
class="compose__action-btn compose__action-btn--record"
|
||||
id="ptt"
|
||||
aria-label="Push to talk"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#mic"></use></svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="compose__action-btn compose__action-btn--send"
|
||||
id="send"
|
||||
aria-label="Send message"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#send"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
6
internal/service/templates/static/footer/footer.gohtml
Normal file
6
internal/service/templates/static/footer/footer.gohtml
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "footer" }}
|
||||
<footer class="footer">
|
||||
{{ template "footer/compose" . }}
|
||||
{{ template "footer/toolbar" . }}
|
||||
</footer>
|
||||
{{ end }}
|
||||
31
internal/service/templates/static/footer/toolbar.gohtml
Normal file
31
internal/service/templates/static/footer/toolbar.gohtml
Normal file
@@ -0,0 +1,31 @@
|
||||
{{ define "footer/toolbar" }}
|
||||
<div class="footer__toolbar">
|
||||
<button
|
||||
type="button"
|
||||
class="footer__toolbar-btn"
|
||||
id="reset"
|
||||
aria-label="Reset conversation"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#reset"></use></svg>
|
||||
</button>
|
||||
<div class="footer__toolbar-spacer"></div>
|
||||
<select id="model" class="footer__select" aria-label="Model">
|
||||
<option value="" disabled selected>
|
||||
Loading...
|
||||
</option>
|
||||
</select>
|
||||
<select id="voice" class="footer__select" aria-label="Voice">
|
||||
<option value="" disabled selected>
|
||||
Loading...
|
||||
</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="footer__toolbar-btn"
|
||||
id="mute"
|
||||
aria-label="Mute"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#volume"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
11
internal/service/templates/static/head.gohtml
Normal file
11
internal/service/templates/static/head.gohtml
Normal file
@@ -0,0 +1,11 @@
|
||||
{{ define "head" }}
|
||||
<head>
|
||||
<meta name="author" content="Chimerical LLC">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, shrink-to-fit= no, interactive-widget=resizes-content">
|
||||
<title>Odidere</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
<script defer src="/static/main.js"></script>
|
||||
</head>
|
||||
{{ end }}
|
||||
5
internal/service/templates/static/index.gohtml
Normal file
5
internal/service/templates/static/index.gohtml
Normal file
@@ -0,0 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
{{ template "head" . }}
|
||||
{{ template "body" . }}
|
||||
</html>
|
||||
7
internal/service/templates/static/main.gohtml
Normal file
7
internal/service/templates/static/main.gohtml
Normal file
@@ -0,0 +1,7 @@
|
||||
{{ define "main" }}
|
||||
<main class="container">
|
||||
<section class="chat" id="chat" aria-live="polite">
|
||||
</section>
|
||||
{{ template "footer" . }}
|
||||
</main>
|
||||
{{ end }}
|
||||
126
internal/service/templates/static/templates.gohtml
Normal file
126
internal/service/templates/static/templates.gohtml
Normal file
@@ -0,0 +1,126 @@
|
||||
{{ define "templates" }}
|
||||
<template id="tpl-user-message">
|
||||
<div class="message message--user">
|
||||
<div class="message__icon" aria-hidden="true">
|
||||
<svg class="icon"><use href="/static/icons.svg#user"></use></svg>
|
||||
</div>
|
||||
<div class="message__body">
|
||||
<div class="message__content"></div>
|
||||
<div class="message__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="inspect"
|
||||
aria-label="Show details"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#inspect"></use></svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="copy"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#copy"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="message__debug">
|
||||
<dl class="message__debug-list"></dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-assistant-message">
|
||||
<div class="message message--assistant">
|
||||
<div class="message__icon" aria-hidden="true">
|
||||
<svg class="icon"><use href="/static/icons.svg#assistant"></use></svg>
|
||||
</div>
|
||||
<div class="message__body">
|
||||
<div class="message__content"></div>
|
||||
<div class="message__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="inspect"
|
||||
aria-label="Show details"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#inspect"></use></svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="message__action-btn"
|
||||
data-action="copy"
|
||||
aria-label="Copy to clipboard"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#copy"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="message__debug">
|
||||
<dl class="message__debug-list"></dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-error-message">
|
||||
<div class="message message--error message--assistant">
|
||||
<div class="message__icon" aria-hidden="true">
|
||||
<svg class="icon"><use href="/static/icons.svg#assistant"></use></svg>
|
||||
</div>
|
||||
<div class="message__body">
|
||||
<div class="message__content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-collapsible">
|
||||
<details class="collapsible">
|
||||
<summary class="collapsible__summary">
|
||||
<span class="collapsible__label"></span>
|
||||
</summary>
|
||||
<div class="collapsible__content">
|
||||
<pre></pre>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<template id="tpl-tool-call">
|
||||
<details class="collapsible collapsible--tool">
|
||||
<summary class="collapsible__summary">
|
||||
<svg class="icon"><use href="/static/icons.svg#tool"></use></svg>
|
||||
<span class="collapsible__label"></span>
|
||||
</summary>
|
||||
<div class="collapsible__content">
|
||||
<div class="collapsible__section">
|
||||
<div class="collapsible__section-label">Arguments</div>
|
||||
<pre class="collapsible__pre" data-args></pre>
|
||||
</div>
|
||||
<div class="collapsible__section collapsible__section--output" hidden>
|
||||
<div class="collapsible__section-label">Output</div>
|
||||
<pre class="collapsible__pre" data-output></pre>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<template id="tpl-attachment-chip">
|
||||
<div class="compose__attachment">
|
||||
<span class="compose__attachment-name"></span>
|
||||
<button
|
||||
type="button"
|
||||
class="compose__attachment-remove"
|
||||
aria-label="Remove attachment"
|
||||
>
|
||||
<svg class="icon"><use href="/static/icons.svg#x-circle"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tpl-debug-row">
|
||||
<dt></dt>
|
||||
<dd></dd>
|
||||
</template>
|
||||
{{ end }}
|
||||
43
internal/service/templates/templates.go
Normal file
43
internal/service/templates/templates.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var static embed.FS
|
||||
|
||||
const fileType = ".gohtml"
|
||||
|
||||
// Parse walks the embedded static directory and parses all .gohtml templates.
|
||||
func Parse() (*template.Template, error) {
|
||||
tmpl := template.New("")
|
||||
|
||||
parseFS := func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(path, fileType) {
|
||||
if _, err := tmpl.ParseFS(static, path); err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to parse template %s: %w",
|
||||
path, err,
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := fs.WalkDir(static, ".", parseFS); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse templates: %w", err)
|
||||
}
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
Reference in New Issue
Block a user