Add user defined system message

This commit is contained in:
dwrz
2026-05-28 00:53:08 +00:00
parent a06833bb8b
commit 9ea4005e96
26 changed files with 652 additions and 222 deletions

View File

@@ -12,7 +12,7 @@ llm:
url: "http://localhost:8081/v1" url: "http://localhost:8081/v1"
key: ${ODIDERE_LLM_KEY} key: ${ODIDERE_LLM_KEY}
model: "default" model: "default"
system_prompt: "You are a helpful voice assistant. Be concise." system_message: "You are a helpful voice assistant. Be concise."
timeout: "5m" timeout: "5m"
tts: tts:

View File

@@ -22,8 +22,8 @@ type Config struct {
Key string `yaml:"key"` Key string `yaml:"key"`
// Model is the model identifier. // Model is the model identifier.
Model string `yaml:"model"` Model string `yaml:"model"`
// SystemPrompt is prepended to all conversations. // SystemMessage is prepended to all conversations.
SystemPrompt string `yaml:"system_prompt"` SystemMessage string `yaml:"system_message"`
// Timeout is the maximum duration for a query (e.g., "5m"). // Timeout is the maximum duration for a query (e.g., "5m").
// Defaults to 5 minutes if empty. // Defaults to 5 minutes if empty.
Timeout string `yaml:"timeout"` Timeout string `yaml:"timeout"`
@@ -53,7 +53,7 @@ type Client struct {
log *slog.Logger log *slog.Logger
model string model string
registry *tool.Registry registry *tool.Registry
systemPrompt string systemMessage string
timeout time.Duration timeout time.Duration
tools []openai.Tool tools []openai.Tool
} }
@@ -72,7 +72,7 @@ func NewClient(
llm := &Client{ llm := &Client{
log: log, log: log,
model: cfg.Model, model: cfg.Model,
systemPrompt: cfg.SystemPrompt, systemMessage: cfg.SystemMessage,
registry: registry, registry: registry,
} }
@@ -122,12 +122,14 @@ func (c *Client) DefaultModel() string {
// Query sends messages to the LLM using the specified model. // Query sends messages to the LLM using the specified model.
// If model is empty, uses the default configured model. // If model is empty, uses the default configured model.
// If systemMessage is non-empty, it overrides the configured system message.
// Returns all messages generated during the query, including tool calls // Returns all messages generated during the query, including tool calls
// and tool results. The final message is the last element in the slice. // and tool results. The final message is the last element in the slice.
func (c *Client) Query( func (c *Client) Query(
ctx context.Context, ctx context.Context,
messages []openai.ChatCompletionMessage, messages []openai.ChatCompletionMessage,
model string, model string,
systemMessage string,
) ([]openai.ChatCompletionMessage, error) { ) ([]openai.ChatCompletionMessage, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout) ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel() defer cancel()
@@ -137,13 +139,19 @@ func (c *Client) Query(
model = c.model model = c.model
} }
// Prepend system prompt, if configured and not already present. // Use per-request system message if provided, otherwise fall back to config.
if c.systemPrompt != "" && (len(messages) == 0 || effectiveMessage := c.systemMessage
if systemMessage != "" {
effectiveMessage = systemMessage
}
// Prepend system message, if configured and not already present.
if effectiveMessage != "" && (len(messages) == 0 ||
messages[0].Role != openai.ChatMessageRoleSystem) { messages[0].Role != openai.ChatMessageRoleSystem) {
messages = append( messages = append(
[]openai.ChatCompletionMessage{{ []openai.ChatCompletionMessage{{
Role: openai.ChatMessageRoleSystem, Role: openai.ChatMessageRoleSystem,
Content: c.systemPrompt, Content: effectiveMessage,
}}, }},
messages..., messages...,
) )
@@ -239,11 +247,13 @@ type StreamEvent struct {
// streams results. Each complete message (assistant reply, tool call, // streams results. Each complete message (assistant reply, tool call,
// tool result) is sent to the events channel as it becomes available. // tool result) is sent to the events channel as it becomes available.
// The channel is closed before returning. // The channel is closed before returning.
// If systemMessage is non-empty, it overrides the configured system message.
// Returns all messages generated during the query. // Returns all messages generated during the query.
func (c *Client) QueryStream( func (c *Client) QueryStream(
ctx context.Context, ctx context.Context,
messages []openai.ChatCompletionMessage, messages []openai.ChatCompletionMessage,
model string, model string,
systemMessage string,
events chan<- StreamEvent, events chan<- StreamEvent,
) error { ) error {
defer close(events) defer close(events)
@@ -256,13 +266,19 @@ func (c *Client) QueryStream(
model = c.model model = c.model
} }
// Prepend system prompt, if configured and not already present. // Use per-request system message if provided, otherwise fall back to config.
if c.systemPrompt != "" && (len(messages) == 0 || effectiveMessage := c.systemMessage
if systemMessage != "" {
effectiveMessage = systemMessage
}
// Prepend system message, if configured and not already present.
if effectiveMessage != "" && (len(messages) == 0 ||
messages[0].Role != openai.ChatMessageRoleSystem) { messages[0].Role != openai.ChatMessageRoleSystem) {
messages = append( messages = append(
[]openai.ChatCompletionMessage{{ []openai.ChatCompletionMessage{{
Role: openai.ChatMessageRoleSystem, Role: openai.ChatMessageRoleSystem,
Content: c.systemPrompt, Content: effectiveMessage,
}}, }},
messages..., messages...,
) )

View File

@@ -69,7 +69,7 @@ func TestConfigValidate(t *testing.T) {
cfg: Config{ cfg: Config{
Key: "sk-test-key", Key: "sk-test-key",
Model: "test-model", Model: "test-model",
SystemPrompt: "You are a helpful assistant.", SystemMessage: "You are a helpful assistant.",
Timeout: "30m", Timeout: "30m",
URL: "http://localhost:8080", URL: "http://localhost:8080",
}, },
@@ -123,7 +123,7 @@ func TestNewClient(t *testing.T) {
cfg: Config{ cfg: Config{
Key: "test-key", Key: "test-key",
Model: "test-model", Model: "test-model",
SystemPrompt: "Test prompt", SystemMessage: "Test message",
Timeout: "5m", Timeout: "5m",
URL: "http://localhost:8080", URL: "http://localhost:8080",
}, },

View File

@@ -311,6 +311,8 @@ type Request struct {
Messages []openai.ChatCompletionMessage `json:"messages"` Messages []openai.ChatCompletionMessage `json:"messages"`
// Model is the LLM model ID. If empty, the default model is used. // Model is the LLM model ID. If empty, the default model is used.
Model string `json:"model,omitempty"` Model string `json:"model,omitempty"`
// SystemMessage overrides the configured system message for this request.
SystemMessage string `json:"system_message,omitempty"`
// Voice is the voice ID for TTS. // Voice is the voice ID for TTS.
Voice string `json:"voice,omitempty"` Voice string `json:"voice,omitempty"`
} }
@@ -455,7 +457,7 @@ func (svc *Service) voice(w http.ResponseWriter, r *http.Request) {
if model == "" { if model == "" {
model = svc.llm.DefaultModel() model = svc.llm.DefaultModel()
} }
msgs, err := svc.llm.Query(ctx, messages, model) msgs, err := svc.llm.Query(ctx, messages, model, req.SystemMessage)
if err != nil { if err != nil {
log.ErrorContext( log.ErrorContext(
ctx, ctx,
@@ -731,7 +733,7 @@ func (svc *Service) voiceStream(w http.ResponseWriter, r *http.Request) {
llmErr error llmErr error
) )
go func() { go func() {
llmErr = svc.llm.QueryStream(ctx, messages, model, events) llmErr = svc.llm.QueryStream(ctx, messages, model, req.SystemMessage, events)
}() }()
// Consume events and send as SSE. // Consume events and send as SSE.

View File

@@ -90,4 +90,9 @@
<path d="m15 9-6 6"/> <path d="m15 9-6 6"/>
<path d="m9 9 6 6"/> <path d="m9 9 6 6"/>
</symbol> </symbol>
<symbol id="settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/>
</symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -593,6 +593,201 @@ body {
color: var(--color-text-muted); color: var(--color-text-muted);
} }
/* ==================== */
/* Settings Modal */
/* ==================== */
.settings-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
padding: var(--s1);
}
.settings-overlay.open {
display: flex;
}
.settings-panel {
background: var(--color-surface);
border: 1px solid var(--color-border-light);
border-radius: var(--radius);
width: 100%;
max-width: 480px;
max-height: 90vh;
overflow-y: auto;
padding: var(--s1);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
}
.settings-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--s1);
}
.settings-panel__title {
font-size: var(--s1);
font-weight: 600;
color: var(--color-text);
margin: 0;
}
/* Close button: remove toolbar border, darken the X. */
#settings-close {
border: none;
color: var(--color-text);
}
@media (hover: hover) {
#settings-close:hover {
border-color: transparent;
color: var(--color-text);
}
}
/* Tabs */
.settings-tabs {
display: flex;
border-bottom: 2px solid var(--color-border-light);
margin-bottom: var(--s1);
}
.settings-tab {
padding: var(--s-2) var(--s-1);
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
font-family: inherit;
font-size: var(--s-1);
color: var(--color-text-muted);
cursor: pointer;
transition: all 0.15s ease;
}
@media (hover: hover) {
.settings-tab:hover {
color: var(--color-text);
}
}
.settings-tab:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: -2px;
}
.settings-tab.active {
color: var(--color-text);
border-bottom-color: var(--color-primary);
font-weight: 500;
}
/* Tab panels */
.settings-tab-panel {
display: none;
}
.settings-tab-panel.active {
display: block;
}
.settings-label {
display: block;
font-size: var(--s-1);
font-weight: 500;
color: var(--color-text);
margin-bottom: var(--s-2);
}
.settings-select {
width: 100%;
height: var(--s2);
padding: 0 var(--s-1);
font-family: inherit;
font-size: var(--s-1);
line-height: var(--s2);
color: var(--color-text);
background: var(--color-surface);
border: 1px solid var(--color-border-light);
border-radius: var(--radius);
cursor: pointer;
margin-bottom: var(--s1);
}
.settings-select:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.settings-textarea {
display: block;
width: 100%;
min-height: calc(var(--s2) * 6);
padding: var(--s-1);
font-family: var(--font-mono);
font-size: var(--s-1);
line-height: 1.5;
color: var(--color-text);
background: var(--color-bg);
border: 1px solid var(--color-border-light);
border-radius: var(--radius);
resize: vertical;
margin-bottom: var(--s-1);
}
.settings-textarea:focus {
outline: 2px solid var(--color-primary);
outline-offset: 1px;
}
.settings-save-row {
display: flex;
justify-content: flex-end;
}
.settings-save-btn {
display: flex;
align-items: center;
justify-content: center;
padding: var(--s-2) var(--s-1);
font-family: inherit;
font-size: var(--s-1);
font-weight: 500;
color: white;
background: var(--color-primary);
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: background-color 0.15s ease;
min-width: var(--s2);
}
@media (hover: hover) {
.settings-save-btn:hover {
background: var(--color-primary-hover);
}
}
.settings-save-btn:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.settings-save-btn--success {
background: var(--color-green);
}
.settings-save-btn .icon {
width: 1rem;
height: 1rem;
}
/* ==================== */ /* ==================== */
/* Animations */ /* Animations */
/* ==================== */ /* ==================== */
@@ -668,4 +863,16 @@ body {
max-width: 128px; max-width: 128px;
font-size: var(--s-1); font-size: var(--s-1);
} }
.settings-overlay {
padding: 0;
}
.settings-panel {
max-width: 100%;
max-height: 100dvh;
height: 100dvh;
border-radius: 0;
border: none;
}
} }

View File

@@ -3,6 +3,7 @@ const ICONS_URL = '/static/icons.svg';
const MODELS_ENDPOINT = '/v1/models'; const MODELS_ENDPOINT = '/v1/models';
const MODEL_KEY = 'odidere_model'; const MODEL_KEY = 'odidere_model';
const STORAGE_KEY = 'odidere_history'; const STORAGE_KEY = 'odidere_history';
const SYSTEM_MESSAGE_KEY = 'odidere_system_message';
const VOICES_ENDPOINT = '/v1/voices'; const VOICES_ENDPOINT = '/v1/voices';
const VOICE_KEY = 'odidere_voice'; const VOICE_KEY = 'odidere_voice';
@@ -42,6 +43,15 @@ class Odidere {
this.$voice = document.getElementById('voice'); this.$voice = document.getElementById('voice');
this.$mute = document.getElementById('mute'); this.$mute = document.getElementById('mute');
// Settings modal
this.$settings = document.getElementById('settings');
this.$settingsOverlay = document.getElementById('settings-overlay');
this.$settingsClose = document.getElementById('settings-close');
this.$settingsTabs = document.querySelectorAll('.settings-tab');
this.$settingsPanels = document.querySelectorAll('.settings-tab-panel');
this.$systemMessageInput = document.getElementById('system-message-input');
this.$saveSystemMessage = document.getElementById('save-system-message');
// Templates // Templates
this.$tplAssistantMessage = document.getElementById( this.$tplAssistantMessage = document.getElementById(
'tpl-assistant-message', 'tpl-assistant-message',
@@ -151,6 +161,28 @@ class Odidere {
}); });
// Mute button // Mute button
this.$mute.addEventListener('click', () => this.#toggleMute()); this.$mute.addEventListener('click', () => this.#toggleMute());
// Settings modal
this.$settings.addEventListener('click', () => this.openSettings());
this.$settingsClose.addEventListener('click', () => this.closeSettings());
this.$settingsOverlay.addEventListener('click', (e) => {
if (e.target === this.$settingsOverlay) this.closeSettings();
});
this.document.addEventListener('keydown', (e) => {
if (
e.key === 'Escape' &&
this.$settingsOverlay.classList.contains('open')
) {
e.preventDefault();
this.closeSettings();
}
});
this.$settingsTabs.forEach(($tab) => {
$tab.addEventListener('click', () => this.#switchTab($tab.dataset.tab));
});
this.$saveSystemMessage.addEventListener('click', () =>
this.#saveSystemMessage(),
);
} }
/** /**
@@ -557,6 +589,10 @@ class Odidere {
voice: this.$voice.value, voice: this.$voice.value,
model: this.$model.value, model: this.$model.value,
}; };
const systemMessage = localStorage.getItem(SYSTEM_MESSAGE_KEY);
if (systemMessage) {
payload.system_message = systemMessage;
}
if (audio) { if (audio) {
payload.audio = await this.#toBase64(audio); payload.audio = await this.#toBase64(audio);
} }
@@ -674,7 +710,11 @@ class Odidere {
} }
// Collect tool results and render once all have arrived. // Collect tool results and render once all have arrived.
if (message.role === 'tool' && pendingTools && pendingTools.assistant) { if (
message.role === 'tool' &&
pendingTools &&
pendingTools.assistant
) {
pendingTools.results.push(message); pendingTools.results.push(message);
// Add to history (server needs it) but don't render yet. // Add to history (server needs it) but don't render yet.
this.#appendHistory([message]); this.#appendHistory([message]);
@@ -849,6 +889,67 @@ class Odidere {
} }
} }
/**
* openSettings opens the settings modal and loads current values.
*/
openSettings() {
this.$settingsOverlay.classList.add('open');
this.#loadSystemMessage();
this.#switchTab('model-voice');
// Focus the close button for accessibility.
this.$settingsClose.focus();
}
/**
* closeSettings closes the settings modal and restores focus.
*/
closeSettings() {
this.$settingsOverlay.classList.remove('open');
this.$settings.focus();
}
/**
* #switchTab switches the active tab in the settings modal.
* @param {string} tabName
*/
#switchTab(tabName) {
this.$settingsTabs.forEach(($tab) => {
const isActive = $tab.dataset.tab === tabName;
$tab.classList.toggle('active', isActive);
$tab.setAttribute('aria-selected', String(isActive));
});
this.$settingsPanels.forEach(($panel) => {
const isActive = $panel.dataset.tabPanel === tabName;
$panel.classList.toggle('active', isActive);
$panel.hidden = !isActive;
});
}
/**
* #saveSystemMessage saves the textarea value to localStorage.
*/
#saveSystemMessage() {
const value = this.$systemMessageInput.value;
localStorage.setItem(SYSTEM_MESSAGE_KEY, value);
// Brief visual feedback on the save button.
this.$saveSystemMessage.replaceChildren(this.#icon('check'));
this.$saveSystemMessage.classList.add('settings-save-btn--success');
setTimeout(() => {
this.$saveSystemMessage.textContent = 'Save';
this.$saveSystemMessage.classList.remove('settings-save-btn--success');
}, 1500);
}
/**
* #loadSystemMessage reads from localStorage and populates the textarea.
*/
#loadSystemMessage() {
const stored = localStorage.getItem(SYSTEM_MESSAGE_KEY);
this.$systemMessageInput.value = stored || '';
}
// ==================== // ====================
// RENDER: SELECTS // RENDER: SELECTS
// ==================== // ====================

View File

@@ -2,5 +2,6 @@
<body> <body>
{{ template "main" . }} {{ template "main" . }}
{{ template "templates" . }} {{ template "templates" . }}
{{ template "modal/settings" . }}
</body> </body>
{{ end }} {{ end }}

View File

@@ -1,5 +1,14 @@
{{ define "footer/toolbar" }} {{ define "footer/toolbar" }}
<div class="footer__toolbar"> <div class="footer__toolbar">
<button
type="button"
class="footer__toolbar-btn"
id="settings"
aria-label="Settings"
>
<svg class="icon"><use href="/static/icons.svg#settings"></use></svg>
</button>
<div class="footer__toolbar-spacer"></div>
<button <button
type="button" type="button"
class="footer__toolbar-btn" class="footer__toolbar-btn"
@@ -8,17 +17,6 @@
> >
<svg class="icon"><use href="/static/icons.svg#reset"></use></svg> <svg class="icon"><use href="/static/icons.svg#reset"></use></svg>
</button> </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 <button
type="button" type="button"
class="footer__toolbar-btn" class="footer__toolbar-btn"

View File

@@ -0,0 +1,83 @@
{{ define "modal/settings" }}
<div class="settings-overlay" id="settings-overlay">
<div class="settings-panel" role="dialog" aria-modal="true"
aria-labelledby="settings-title">
<div class="settings-panel__header">
<h2 class="settings-panel__title" id="settings-title">Settings</h2>
<button
type="button"
class="footer__toolbar-btn"
id="settings-close"
aria-label="Close settings"
>
<svg class="icon"><use href="/static/icons.svg#close"></use></svg>
</button>
</div>
<div class="settings-tabs" role="tablist">
<button
type="button"
class="settings-tab active"
role="tab"
data-tab="model-voice"
aria-selected="true"
aria-controls="panel-model-voice"
>Model &amp; Voice</button>
<button
type="button"
class="settings-tab"
role="tab"
data-tab="system-message"
aria-selected="false"
aria-controls="panel-system-message"
>System Message</button>
</div>
<div class="settings-tab-panels">
<div
class="settings-tab-panel active"
role="tabpanel"
data-tab-panel="model-voice"
id="panel-model-voice"
aria-labelledby="tab-model-voice"
>
<label class="settings-label" for="model">Model</label>
<select id="model" class="settings-select" aria-label="Model">
<option value="" disabled selected>Loading...</option>
</select>
<label class="settings-label" for="voice">Voice</label>
<select id="voice" class="settings-select" aria-label="Voice">
<option value="" disabled selected>Loading...</option>
</select>
</div>
<div
class="settings-tab-panel"
role="tabpanel"
data-tab-panel="system-message"
id="panel-system-message"
aria-labelledby="tab-system-message"
hidden
>
<label class="settings-label" for="system-message-input">
System Message
</label>
<textarea
id="system-message-input"
class="settings-textarea"
rows="8"
placeholder="Enter system message..."
></textarea>
<div class="settings-save-row">
<button
type="button"
class="settings-save-btn"
id="save-system-message"
>Save</button>
</div>
</div>
</div>
</div>
</div>
{{ end }}

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build js
// +build js // +build js
package uuid package uuid

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !js
// +build !js // +build !js
package uuid package uuid

View File

@@ -25,7 +25,6 @@ var jsonNull = []byte("null")
// } else { // } else {
// // NULL value // // NULL value
// } // }
//
type NullUUID struct { type NullUUID struct {
UUID UUID UUID UUID
Valid bool // Valid is true if UUID is not NULL Valid bool // Valid is true if UUID is not NULL

View File

@@ -187,10 +187,12 @@ func Must(uuid UUID, err error) UUID {
} }
// Validate returns an error if s is not a properly formatted UUID in one of the following formats: // Validate returns an error if s is not a properly formatted UUID in one of the following formats:
//
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
//
// It returns an error if the format is invalid, otherwise nil. // It returns an error if the format is invalid, otherwise nil.
func Validate(s string) error { func Validate(s string) error {
switch len(s) { switch len(s) {

View File

@@ -165,7 +165,6 @@ func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool {
// - 1 event for DOCUMENT-START // - 1 event for DOCUMENT-START
// - 2 events for SEQUENCE-START // - 2 events for SEQUENCE-START
// - 3 events for MAPPING-START // - 3 events for MAPPING-START
//
func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool {
if emitter.events_head == len(emitter.events) { if emitter.events_head == len(emitter.events) {
return true return true

50
vendor/gopkg.in/yaml.v3/parserc.go generated vendored
View File

@@ -227,6 +227,7 @@ func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool
// Parse the production: // Parse the production:
// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END // stream ::= STREAM-START implicit_document? explicit_document* STREAM-END
//
// ************ // ************
func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
@@ -249,8 +250,11 @@ func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t)
// Parse the productions: // Parse the productions:
// implicit_document ::= block_node DOCUMENT-END* // implicit_document ::= block_node DOCUMENT-END*
//
// * // *
//
// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* // explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
//
// ************************* // *************************
func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool {
@@ -356,8 +360,8 @@ func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t
// Parse the productions: // Parse the productions:
// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* // explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
// ***********
// //
// ***********
func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -379,9 +383,10 @@ func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event
// Parse the productions: // Parse the productions:
// implicit_document ::= block_node DOCUMENT-END* // implicit_document ::= block_node DOCUMENT-END*
// *************
// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
// //
// *************
//
// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END*
func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -428,29 +433,40 @@ func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t)
// Parse the productions: // Parse the productions:
// block_node_or_indentless_sequence ::= // block_node_or_indentless_sequence ::=
//
// ALIAS // ALIAS
// ***** // *****
// | properties (block_content | indentless_block_sequence)? // | properties (block_content | indentless_block_sequence)?
// ********** * // ********** *
// | block_content | indentless_block_sequence // | block_content | indentless_block_sequence
// * // *
//
// block_node ::= ALIAS // block_node ::= ALIAS
//
// ***** // *****
// | properties block_content? // | properties block_content?
// ********** * // ********** *
// | block_content // | block_content
// * // *
//
// flow_node ::= ALIAS // flow_node ::= ALIAS
//
// ***** // *****
// | properties flow_content? // | properties flow_content?
// ********** * // ********** *
// | flow_content // | flow_content
// * // *
//
// properties ::= TAG ANCHOR? | ANCHOR TAG? // properties ::= TAG ANCHOR? | ANCHOR TAG?
//
// ************************* // *************************
//
// block_content ::= block_collection | flow_collection | SCALAR // block_content ::= block_collection | flow_collection | SCALAR
//
// ****** // ******
//
// flow_content ::= flow_collection | SCALAR // flow_content ::= flow_collection | SCALAR
//
// ****** // ******
func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool {
//defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)()
@@ -682,8 +698,8 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
// Parse the productions: // Parse the productions:
// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END // block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END
// ******************** *********** * *********
// //
// ******************** *********** * *********
func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
if first { if first {
token := peek_token(parser) token := peek_token(parser)
@@ -740,6 +756,7 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e
// Parse the productions: // Parse the productions:
// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ // indentless_sequence ::= (BLOCK-ENTRY block_node?)+
//
// *********** * // *********** *
func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
@@ -805,6 +822,7 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) {
// Parse the productions: // Parse the productions:
// block_mapping ::= BLOCK-MAPPING_START // block_mapping ::= BLOCK-MAPPING_START
//
// ******************* // *******************
// ((KEY block_node_or_indentless_sequence?)? // ((KEY block_node_or_indentless_sequence?)?
// *** * // *** *
@@ -812,7 +830,6 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) {
// //
// BLOCK-END // BLOCK-END
// ********* // *********
//
func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
if first { if first {
token := peek_token(parser) token := peek_token(parser)
@@ -886,8 +903,6 @@ func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_even
// (VALUE block_node_or_indentless_sequence?)?)* // (VALUE block_node_or_indentless_sequence?)?)*
// ***** * // ***** *
// BLOCK-END // BLOCK-END
//
//
func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -915,6 +930,7 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev
// Parse the productions: // Parse the productions:
// flow_sequence ::= FLOW-SEQUENCE-START // flow_sequence ::= FLOW-SEQUENCE-START
//
// ******************* // *******************
// (flow_sequence_entry FLOW-ENTRY)* // (flow_sequence_entry FLOW-ENTRY)*
// * ********** // * **********
@@ -922,9 +938,10 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev
// * // *
// FLOW-SEQUENCE-END // FLOW-SEQUENCE-END
// ***************** // *****************
// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// *
// //
// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
//
// *
func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
if first { if first {
token := peek_token(parser) token := peek_token(parser)
@@ -987,11 +1004,10 @@ func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_ev
return true return true
} }
//
// Parse the productions: // Parse the productions:
// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// *** *
// //
// *** *
func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -1011,8 +1027,8 @@ func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, ev
// Parse the productions: // Parse the productions:
// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// ***** *
// //
// ***** *
func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -1035,8 +1051,8 @@ func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t,
// Parse the productions: // Parse the productions:
// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// *
// //
// *
func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {
@@ -1053,6 +1069,7 @@ func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, ev
// Parse the productions: // Parse the productions:
// flow_mapping ::= FLOW-MAPPING-START // flow_mapping ::= FLOW-MAPPING-START
//
// ****************** // ******************
// (flow_mapping_entry FLOW-ENTRY)* // (flow_mapping_entry FLOW-ENTRY)*
// * ********** // * **********
@@ -1060,9 +1077,9 @@ func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, ev
// ****************** // ******************
// FLOW-MAPPING-END // FLOW-MAPPING-END
// **************** // ****************
// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// * *** *
// //
// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// - *** *
func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool {
if first { if first {
token := peek_token(parser) token := peek_token(parser)
@@ -1128,8 +1145,7 @@ func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event
// Parse the productions: // Parse the productions:
// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? // flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)?
// * ***** * // - ***** *
//
func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool {
token := peek_token(parser) token := peek_token(parser)
if token == nil { if token == nil {

View File

@@ -1614,11 +1614,11 @@ func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool {
// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. // Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token.
// //
// Scope: // Scope:
//
// %YAML 1.1 # a comment \n // %YAML 1.1 # a comment \n
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// %TAG !yaml! tag:yaml.org,2002: \n // %TAG !yaml! tag:yaml.org,2002: \n
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool {
// Eat '%'. // Eat '%'.
start_mark := parser.mark start_mark := parser.mark
@@ -1719,11 +1719,11 @@ func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool
// Scan the directive name. // Scan the directive name.
// //
// Scope: // Scope:
//
// %YAML 1.1 # a comment \n // %YAML 1.1 # a comment \n
// ^^^^ // ^^^^
// %TAG !yaml! tag:yaml.org,2002: \n // %TAG !yaml! tag:yaml.org,2002: \n
// ^^^ // ^^^
//
func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool {
// Consume the directive name. // Consume the directive name.
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
@@ -1758,6 +1758,7 @@ func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark
// Scan the value of VERSION-DIRECTIVE. // Scan the value of VERSION-DIRECTIVE.
// //
// Scope: // Scope:
//
// %YAML 1.1 # a comment \n // %YAML 1.1 # a comment \n
// ^^^^^^ // ^^^^^^
func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool {
@@ -1797,6 +1798,7 @@ const max_number_length = 2
// Scan the version number of VERSION-DIRECTIVE. // Scan the version number of VERSION-DIRECTIVE.
// //
// Scope: // Scope:
//
// %YAML 1.1 # a comment \n // %YAML 1.1 # a comment \n
// ^ // ^
// %YAML 1.1 # a comment \n // %YAML 1.1 # a comment \n
@@ -1834,9 +1836,9 @@ func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark
// Scan the value of a TAG-DIRECTIVE token. // Scan the value of a TAG-DIRECTIVE token.
// //
// Scope: // Scope:
//
// %TAG !yaml! tag:yaml.org,2002: \n // %TAG !yaml! tag:yaml.org,2002: \n
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool {
var handle_value, prefix_value []byte var handle_value, prefix_value []byte

5
vendor/gopkg.in/yaml.v3/yaml.go generated vendored
View File

@@ -18,7 +18,6 @@
// Source code and other details for the project are available at GitHub: // Source code and other details for the project are available at GitHub:
// //
// https://github.com/go-yaml/yaml // https://github.com/go-yaml/yaml
//
package yaml package yaml
import ( import (
@@ -84,7 +83,6 @@ type Marshaler interface {
// //
// See the documentation of Marshal for the format of tags and a list of // See the documentation of Marshal for the format of tags and a list of
// supported tag options. // supported tag options.
//
func Unmarshal(in []byte, out interface{}) (err error) { func Unmarshal(in []byte, out interface{}) (err error) {
return unmarshal(in, out, false) return unmarshal(in, out, false)
} }
@@ -214,7 +212,6 @@ func unmarshal(in []byte, out interface{}, strict bool) (err error) {
// } // }
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" // yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" // yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
//
func Marshal(in interface{}) (out []byte, err error) { func Marshal(in interface{}) (out []byte, err error) {
defer handleErr(&err) defer handleErr(&err)
e := newEncoder() e := newEncoder()
@@ -368,7 +365,6 @@ const (
// //
// var person Node // var person Node
// err := yaml.Unmarshal(data, &person) // err := yaml.Unmarshal(data, &person)
//
type Node struct { type Node struct {
// Kind defines whether the node is a document, a mapping, a sequence, // Kind defines whether the node is a document, a mapping, a sequence,
// a scalar value, or an alias to another node. The specific data type of // a scalar value, or an alias to another node. The specific data type of
@@ -421,7 +417,6 @@ func (n *Node) IsZero() bool {
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0 n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
} }
// LongTag returns the long form of the tag that indicates the data type for // LongTag returns the long form of the tag that indicates the data type for
// the node. If the Tag field isn't explicitly defined, one will be computed // the node. If the Tag field isn't explicitly defined, one will be computed
// based on the node properties. // based on the node properties.

6
vendor/gopkg.in/yaml.v3/yamlh.go generated vendored
View File

@@ -438,7 +438,9 @@ type yaml_document_t struct {
// The number of written bytes should be set to the size_read variable. // The number of written bytes should be set to the size_read variable.
// //
// [in,out] data A pointer to an application data specified by // [in,out] data A pointer to an application data specified by
//
// yaml_parser_set_input(). // yaml_parser_set_input().
//
// [out] buffer The buffer to write the data from the source. // [out] buffer The buffer to write the data from the source.
// [in] size The size of the buffer. // [in] size The size of the buffer.
// [out] size_read The actual number of bytes read from the source. // [out] size_read The actual number of bytes read from the source.
@@ -639,7 +641,6 @@ type yaml_parser_t struct {
} }
type yaml_comment_t struct { type yaml_comment_t struct {
scan_mark yaml_mark_t // Position where scanning for comments started scan_mark yaml_mark_t // Position where scanning for comments started
token_mark yaml_mark_t // Position after which tokens will be associated with this comment token_mark yaml_mark_t // Position after which tokens will be associated with this comment
start_mark yaml_mark_t // Position of '#' comment mark start_mark yaml_mark_t // Position of '#' comment mark
@@ -659,13 +660,14 @@ type yaml_comment_t struct {
// @a buffer to the output. // @a buffer to the output.
// //
// @param[in,out] data A pointer to an application data specified by // @param[in,out] data A pointer to an application data specified by
//
// yaml_emitter_set_output(). // yaml_emitter_set_output().
//
// @param[in] buffer The buffer with bytes to be written. // @param[in] buffer The buffer with bytes to be written.
// @param[in] size The size of the buffer. // @param[in] size The size of the buffer.
// //
// @returns On success, the handler should return @c 1. If the handler failed, // @returns On success, the handler should return @c 1. If the handler failed,
// the returned value should be @c 0. // the returned value should be @c 0.
//
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
type yaml_emitter_state_t int type yaml_emitter_state_t int