parse LLM chunk fix, alpine store state save/load
frdel committed
Dec 2, 2025 at 12:36 UTC
a92b2627c8011267a7df7f8ea5971b546d1473a4
2 files changed
+78
-2
models.py
+2
-2
@@ -813,7 +813,7 @@ def _parse_chunk(chunk: Any) -> ChatChunk:
813
message.get("content", "")
814
if isinstance(message, dict)
815
else getattr(message, "content", "")
816
- )
816
+ ) or ""
817
reasoning_delta = (
818
delta.get("reasoning_content", "")
819
if isinstance(delta, dict)
@@ -822,7 +822,7 @@ def _parse_chunk(chunk: Any) -> ChatChunk:
822
message.get("reasoning_content", "")
823
if isinstance(message, dict)
824
else getattr(message, "reasoning_content", "")
825
- )
825
+ ) or ""
826
827
return ChatChunk(reasoning_delta=reasoning_delta, response_delta=response_delta)
828
webui/js/AlpineStore.js
+76
@@ -44,4 +44,80 @@ export function createStore(name, initialState) {
44
*/
45
export function getStore(name) {
46
return /** @type {T | undefined} */ (stores.get(name));
47
+}
48
+
49
+/**
50
+ * Save current state of a store into a plain object, with optional include/exclude filters.
51
+ * If exclude (blacklist) is provided and non-empty, everything except excluded keys is saved.
52
+ * Otherwise, if include (whitelist) is provided and non-empty, only included keys are saved.
53
+ * If both are empty, all own enumerable properties are saved.
54
+ * @param {object} store
55
+ * @param {string[]} [include]
56
+ * @param {string[]} [exclude]
57
+ * @returns {object}
58
+ */
59
+export function saveState(store, include = [], exclude = []) {
60
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
61
+ const hasInclude = !hasExclude && Array.isArray(include) && include.length > 0;
62
+
63
+ /** @type {Record<string, any>} */
64
+ const snapshot = {};
65
+
66
+ for (const key of Object.keys(store)) {
67
+ if (hasExclude) {
68
+ if (exclude.includes(key)) continue;
69
+ } else if (hasInclude) {
70
+ if (!include.includes(key)) continue;
71
+ }
72
+
73
+ const value = store[key];
74
+ if (typeof value === "function") continue;
75
+
76
+ if (Array.isArray(value)) {
77
+ snapshot[key] = value.map((item) =>
78
+ typeof item === "object" && item !== null ? { ...item } : item
79
+ );
80
+ } else if (typeof value === "object" && value !== null) {
81
+ snapshot[key] = { ...value };
82
+ } else {
83
+ snapshot[key] = value;
84
+ }
85
+ }
86
+
87
+ return snapshot;
88
+}
89
+
90
+/**
91
+ * Load a previously saved state object back into a store, honoring include/exclude filters.
92
+ * Filtering rules are the same as in saveState.
93
+ * @param {object} store
94
+ * @param {object} state
95
+ * @param {string[]} [include]
96
+ * @param {string[]} [exclude]
97
+ */
98
+export function loadState(store, state, include = [], exclude = []) {
99
+ if (!state) return;
100
+
101
+ const hasExclude = Array.isArray(exclude) && exclude.length > 0;
102
+ const hasInclude = !hasExclude && Array.isArray(include) && include.length > 0;
103
+
104
+ for (const key of Object.keys(state)) {
105
+ if (hasExclude) {
106
+ if (exclude.includes(key)) continue;
107
+ } else if (hasInclude) {
108
+ if (!include.includes(key)) continue;
109
+ }
110
+
111
+ const value = state[key];
112
+
113
+ if (Array.isArray(value)) {
114
+ store[key] = value.map((item) =>
115
+ typeof item === "object" && item !== null ? { ...item } : item
116
+ );
117
+ } else if (typeof value === "object" && value !== null) {
118
+ store[key] = { ...value };
119
+ } else {
120
+ store[key] = value;
121
+ }
122
+ }
123
}
\ No newline at end of file