super-stt-app/src/daemon/client/v1/contexts/mod.rs
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-3.0-only |
2 | | //! `/context/...` — the named dictation contexts and which one is in force. |
3 | | //! |
4 | | //! Contract: `docs/protocol/endpoints/v1/context.md`. |
5 | | //! |
6 | | //! A context is what the user is dictating: a `prompt` for a model that follows |
7 | | //! instructions, a `vocabulary` of terms for one that does not. The daemon owns |
8 | | //! the set, one is active at a time, and a backend may be pointed at a different |
9 | | //! one — see [`backend`]. |
10 | | //! |
11 | | //! Hand-written rather than generated by the settings macros, for the same |
12 | | //! reason the daemon's side is: those macros are for a path with one value and |
13 | | //! no parameter, and every path here has an id in it. |
14 | | |
15 | | pub(crate) mod backend; |
16 | | |
17 | | use crate::daemon::client::internal::response::require_unit; |
18 | | use crate::daemon::client::internal::session::with_settings_token; |
19 | | use serde::Deserialize; |
20 | | use super_stt_shared::daemon::http_client::HttpResult; |
21 | | use super_stt_shared::daemon::http_client::transport; |
22 | | use super_stt_shared::models::contexts::DictationContext; |
23 | | |
24 | | /// Every context, and which one is in force. |
25 | | #[derive(Debug, Clone, Default, Deserialize)] |
26 | | pub struct ContextCatalog { |
27 | | /// The id of the context in force, or `None` when none is. |
28 | | #[serde(default)] |
29 | | pub active: Option<String>, |
30 | | /// In the order the user arranged them, which is the order to render. |
31 | | #[serde(default)] |
32 | | pub contexts: Vec<DictationContext>, |
33 | | } |
34 | | |
35 | | /// One context, as the daemon stored it. |
36 | | #[derive(Debug, Clone, Deserialize)] |
37 | | struct OneContext { |
38 | | context: DictationContext, |
39 | | } |
40 | | |
41 | | /// The active selection, as the daemon reports it after a write. |
42 | | /// |
43 | | /// The object is not read here: `GET /context/list` already carries both the |
44 | | /// list and the active id, and the page re-reads that after every write anyway |
45 | | /// — the selection is the one change a *different* row also has to show. This |
46 | | /// type exists so the write's answer is parsed rather than discarded, which is |
47 | | /// what makes a `404` on an unknown id an error instead of a silent no-op. |
48 | | #[derive(Debug, Clone, Default, Deserialize)] |
49 | | pub struct ActiveContext { |
50 | | #[serde(default)] |
51 | | id: Option<String>, |
52 | | } |
53 | | |
54 | | /// `GET /context/list` — every context plus the active id. |
55 | 0 | pub async fn list_contexts() -> HttpResult<ContextCatalog> { |
56 | 0 | with_settings_token(|socket, token| async move { |
57 | 0 | transport::get_json::<ContextCatalog>(socket, &token, "/context/list").await |
58 | 0 | }) |
59 | 0 | .await |
60 | 0 | } |
61 | | |
62 | | /// `POST /context/{id}` — create or replace, answering with the context as |
63 | | /// stored. |
64 | | /// |
65 | | /// Reading the answer rather than echoing what was sent is the point: the |
66 | | /// daemon drops blank vocabulary terms and trims the rest, so the version that |
67 | | /// comes back is the one the list should show. |
68 | 0 | pub async fn set_context(context: DictationContext) -> HttpResult<DictationContext> { |
69 | 0 | with_settings_token(move |socket, token| { |
70 | 0 | let context = context.clone(); |
71 | 0 | async move { |
72 | 0 | let path = format!("/context/{}", urlencoding::encode(&context.id)); |
73 | 0 | let body = serde_json::json!({ |
74 | 0 | "name": context.name, |
75 | 0 | "prompt": context.prompt, |
76 | 0 | "vocabulary": context.vocabulary, |
77 | | }); |
78 | 0 | transport::post_json::<OneContext>(socket, &token, &path, &body) |
79 | 0 | .await |
80 | 0 | .map(|answer| answer.context) |
81 | 0 | } |
82 | 0 | }) |
83 | 0 | .await |
84 | 0 | } |
85 | | |
86 | | /// `DELETE /context/{id}`. |
87 | 0 | pub async fn delete_context(id: String) -> HttpResult<()> { |
88 | 0 | with_settings_token(move |socket, token| { |
89 | 0 | let id = id.clone(); |
90 | 0 | async move { |
91 | 0 | let path = format!("/context/{}", urlencoding::encode(&id)); |
92 | 0 | let resp = transport::settings_delete(socket, &token, &path).await?; |
93 | 0 | require_unit(resp, "delete_context") |
94 | 0 | } |
95 | 0 | }) |
96 | 0 | .await |
97 | 0 | } |
98 | | |
99 | | /// `POST /context/active` — put a context in force, or (`None`) leave none |
100 | | /// active. Answers with the id now in force. |
101 | 0 | pub async fn set_active_context(id: Option<String>) -> HttpResult<Option<String>> { |
102 | 0 | with_settings_token(move |socket, token| { |
103 | 0 | let id = id.clone(); |
104 | 0 | async move { |
105 | 0 | let body = serde_json::json!({ "id": id }); |
106 | 0 | transport::post_json::<ActiveContext>(socket, &token, "/context/active", &body) |
107 | 0 | .await |
108 | 0 | .map(|answer| answer.id) |
109 | 0 | } |
110 | 0 | }) |
111 | 0 | .await |
112 | 0 | } |