memhouse 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-BsgjOSTv.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-CCPh7a2Z.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-DV6ONi_F.css">
12
12
  </head>
13
13
  <body>
@@ -21,7 +21,10 @@ const TYPED_EXAMPLES = [
21
21
  { label: 'Token usage by editor', sql: `SELECT source, sum(input_tokens) AS input_tokens, sum(output_tokens) AS output_tokens FROM sessions_v GROUP BY source ORDER BY input_tokens DESC` },
22
22
  { label: 'Sessions by mode', sql: `SELECT mode, count() AS count FROM sessions_v WHERE mode != '' GROUP BY mode ORDER BY count DESC` },
23
23
  { label: 'Hourly distribution', sql: `SELECT toHour(ts) AS hour, count() AS count FROM messages GROUP BY hour ORDER BY hour` },
24
- { label: 'Full-text search', sql: `SELECT session_id, any(source) AS source, count() AS hits FROM messages WHERE text_ngram LIKE '%clickhouse%' GROUP BY session_id ORDER BY hits DESC LIMIT 10` },
24
+ // Grouped by (session_id, user_id), matching the table's ORDER BY. In a team-pool
25
+ // house two members can ship the same adapter-local session id; grouping on
26
+ // session_id alone would merge them and make any(source) an arbitrary member's.
27
+ { label: 'Full-text search', sql: `SELECT session_id, user_id, any(source) AS source, count() AS hits FROM messages WHERE text_ngram LIKE '%clickhouse%' GROUP BY session_id, user_id ORDER BY hits DESC LIMIT 10` },
25
28
  ]
26
29
  const LEGACY_EXAMPLES = [
27
30
  { label: 'Sessions per editor', sql: `SELECT source, count() AS count FROM chats GROUP BY source ORDER BY count DESC` },
@@ -36,8 +39,13 @@ const LEGACY_EXAMPLES = [
36
39
 
37
40
  export default function SqlViewer() {
38
41
  const { dark } = useTheme()
39
- const [examples, setExamples] = useState(TYPED_EXAMPLES)
40
- const [sql, setSql] = useState(TYPED_EXAMPLES[0].sql)
42
+ // Three states, and they must stay distinguishable: 'loading' while /api/schema is
43
+ // in flight, an array once the house's flavor is known, and null when the request
44
+ // finished without telling us. Guessing wrong offers queries against tables that do
45
+ // not exist, so nothing is offered until the answer is in — but a page that is
46
+ // merely still loading must not claim detection failed.
47
+ const [examples, setExamples] = useState('loading')
48
+ const [sql, setSql] = useState('')
41
49
  const [result, setResult] = useState(null)
42
50
  const [error, setError] = useState(null)
43
51
  const [loading, setLoading] = useState(false)
@@ -50,15 +58,26 @@ export default function SqlViewer() {
50
58
  useEffect(() => {
51
59
  fetchSchema().then(s => {
52
60
  setSchema(s)
53
- // Flavor detection: the typed house always has the sessions_v rollup.
54
- const ex = s?.tables?.includes('sessions_v') ? TYPED_EXAMPLES : LEGACY_EXAMPLES
61
+ // Three outcomes, not two. The typed house has the sessions_v rollup; the
62
+ // legacy one has chats. Anything else — an error object with no `tables`, a
63
+ // half-applied schema with neither marker — is indeterminate, and offering
64
+ // either set would hand the user queries that error.
65
+ const tables = s?.tables
66
+ const ex = !Array.isArray(tables) ? null
67
+ : tables.includes('sessions_v') ? TYPED_EXAMPLES
68
+ : tables.includes('chats') ? LEGACY_EXAMPLES
69
+ : null
55
70
  setExamples(ex)
71
+ if (!ex) return
56
72
  // Swap the prefilled query only if the user hasn't edited it yet.
57
- setSql(prev => (prev === TYPED_EXAMPLES[0].sql || prev === LEGACY_EXAMPLES[0].sql) ? ex[0].sql : prev)
58
- }).catch(() => {})
73
+ setSql(prev => (prev === '' || prev === TYPED_EXAMPLES[0].sql || prev === LEGACY_EXAMPLES[0].sql) ? ex[0].sql : prev)
74
+ }).catch(() => setExamples(null))
59
75
  }, [])
60
76
 
61
77
  const runQuery = async () => {
78
+ // Guard here, not only on the button: ⌘+Enter reaches this directly, and an
79
+ // empty submit comes back as a bare "sql string required" from /api/query.
80
+ if (!sql.trim() || loading) return
62
81
  setLoading(true)
63
82
  setError(null)
64
83
  setResult(null)
@@ -195,9 +214,16 @@ export default function SqlViewer() {
195
214
  </div>
196
215
  )}
197
216
 
198
- {/* Example queries */}
217
+ {/* Example queries — withheld until the schema flavor is known, since the
218
+ typed and legacy sets target entirely different tables. */}
199
219
  <div className="flex flex-wrap gap-1">
200
- {examples.map((q, i) => (
220
+ {examples === 'loading' ? (
221
+ <span className="text-[11px]" style={txt2Style}>loading examples…</span>
222
+ ) : examples === null ? (
223
+ <span className="text-[11px]" style={txt2Style}>
224
+ examples unavailable — could not determine the schema from /api/schema
225
+ </span>
226
+ ) : examples.map((q, i) => (
201
227
  <button
202
228
  key={i}
203
229
  onClick={() => setSql(q.sql)}