opencami 1.9.1 → 2.0.0
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/dist/client/assets/{CSPContext-B3PAVjBL.js → CSPContext-CrlIQW7-.js} +1 -1
- package/dist/client/assets/{DirectionContext-CR5CCisG.js → DirectionContext-X-0CRn1O.js} +1 -1
- package/dist/client/assets/_sessionKey-yNQ57svB.js +23 -0
- package/dist/client/assets/agents-WqWjsymD.js +2 -0
- package/dist/client/assets/{agents-screen--ZMzN5cB.js → agents-screen-BhOVp_S6.js} +1 -1
- package/dist/client/assets/bots-DNqiFT7w.js +2 -0
- package/dist/client/assets/{bots-screen-C_uJBNwI.js → bots-screen-DLFd0ydi.js} +1 -1
- package/dist/client/assets/button-D0n2Qsd_.js +1 -0
- package/dist/client/assets/{composite-DArWbFHm.js → composite-GtKwZKbV.js} +1 -1
- package/dist/client/assets/{connect-B48HecjN.js → connect-vQWL0_11.js} +1 -1
- package/dist/client/assets/{dashboard-BVKx9FMy.js → dashboard-Knwc61i1.js} +1 -1
- package/dist/client/assets/{event-BCwqPPkP.js → event-CHpdjYFR.js} +1 -1
- package/dist/client/assets/file-explorer-screen-FoYNs9zK.js +1 -0
- package/dist/client/assets/files-BtR_gArr.js +2 -0
- package/dist/client/assets/follow-up-suggestions-DVXNLqga.js +5 -0
- package/dist/client/assets/{index-0gdwm1_H.js → index-CNPHef4O.js} +1 -1
- package/dist/client/assets/{index-CGeJcqZ3.js → index-CTT0Y1ya.js} +1 -1
- package/dist/client/assets/{keyboard-shortcuts-dialog-CK_XTzLr.js → keyboard-shortcuts-dialog-DP8ptQ7N.js} +1 -1
- package/dist/client/assets/{main-B2CrcRuC.js → main-xPlWrMhO.js} +3 -3
- package/dist/client/assets/{markdown-CdkuX06F.js → markdown-3Js_RbUp.js} +1 -1
- package/dist/client/assets/memory-BRGPq5t6.js +2 -0
- package/dist/client/assets/{memory-screen-C1RfLy-d.js → memory-screen-nzRra2Qi.js} +1 -1
- package/dist/client/assets/{menu-CIwnliij.js → menu-BnSEqetd.js} +1 -1
- package/dist/client/assets/{opencami-logo-SVuYD55V.js → opencami-logo-B_hLbomw.js} +1 -1
- package/dist/client/assets/{proxy-BijR8W1L.js → proxy-BnlGpgC1.js} +1 -1
- package/dist/client/assets/{react-DWx7OvUo.js → react-BgjQyJHw.js} +1 -1
- package/dist/client/assets/{search-dialog-CB4KE8ec.js → search-dialog-Bib2QY9u.js} +1 -1
- package/dist/client/assets/{search-sources-badge-B8Z-8sSf.js → search-sources-badge-COHcYFRB.js} +1 -1
- package/dist/client/assets/{session-export-dialog-tDiFuv3a.js → session-export-dialog-ooPnfHh_.js} +1 -1
- package/dist/client/assets/settings-dialog-B8mz99u-.js +1 -0
- package/dist/client/assets/skills-DA9J_tsC.js +2 -0
- package/dist/client/assets/{skills-panel-fjJQVMog.js → skills-panel-D2uMdCHp.js} +1 -1
- package/dist/client/assets/styles-CWabEzNU.css +1 -0
- package/dist/client/assets/{switch-Bn5uei2k.js → switch-BUQ0qH6r.js} +1 -1
- package/dist/client/assets/{tabs-DOBNAUVE.js → tabs-BEyU6TjN.js} +1 -1
- package/dist/client/assets/{thinking-dfGrFAMV.js → thinking-CA48yhOE.js} +1 -1
- package/dist/client/assets/{tooltip-DOKkNFvu.js → tooltip-CcIdgcV0.js} +1 -1
- package/dist/client/assets/{use-file-explorer-state-BAa6Cxyr.js → use-file-explorer-state-CN_IJGcd.js} +2 -2
- package/dist/client/assets/{useBaseUiId-DFpBD0sg.js → useBaseUiId-ClbEYEil.js} +1 -1
- package/dist/client/assets/useCompositeItem-B_OxfJee.js +1 -0
- package/dist/client/assets/{useControlled-CQHE0ITz.js → useControlled-CyT-lqbs.js} +1 -1
- package/dist/client/assets/{useMutation-BFl-7GnD.js → useMutation-eQUrsn-X.js} +1 -1
- package/dist/client/assets/{useOnFirstRender-DlXHIIGk.js → useOnFirstRender-CR_o2MK_.js} +1 -1
- package/dist/client/assets/{useQuery-D-sF8Tld.js → useQuery-k6EMRoMD.js} +1 -1
- package/dist/server/assets/{_sessionKey-D8TGrDRM.js → _sessionKey-CaFqmyhU.js} +56 -263
- package/dist/server/assets/{_tanstack-start-manifest_v-DalBo2bY.js → _tanstack-start-manifest_v-P3skSR3R.js} +1 -1
- package/dist/server/assets/{follow-up-suggestions-C65ptDij.js → follow-up-suggestions-DHv2_XzB.js} +13 -74
- package/dist/server/assets/{index-gRco4Ina.js → index-C7lmufwX.js} +1 -1
- package/dist/server/assets/{router-DaKDqc9w.js → router-X2L0PDPI.js} +124 -287
- package/dist/server/assets/{search-dialog-DSSK93kq.js → search-dialog-CXhofdoP.js} +2 -2
- package/dist/server/assets/{settings-dialog-DyWNblva.js → settings-dialog-CPdftvjz.js} +1 -254
- package/dist/server/assets/{thinking-CU0FRlzT.js → thinking-YkRSlXtf.js} +2 -2
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-Bg_9uype.js +0 -23
- package/dist/client/assets/agents-BiTHBb6Z.js +0 -2
- package/dist/client/assets/bots-DxhRnQp5.js +0 -2
- package/dist/client/assets/button-BciDmec0.js +0 -1
- package/dist/client/assets/file-explorer-screen-DPs-FWeA.js +0 -1
- package/dist/client/assets/files-SEycwYCa.js +0 -2
- package/dist/client/assets/follow-up-suggestions-BPjWBpiy.js +0 -5
- package/dist/client/assets/memory-C7lKdkmc.js +0 -2
- package/dist/client/assets/settings-dialog-aL-AH4Rt.js +0 -1
- package/dist/client/assets/skills-D1T6uemU.js +0 -2
- package/dist/client/assets/styles-D0L88B64.css +0 -1
- package/dist/client/assets/useCompositeItem-B-Axq9-D.js +0 -1
|
@@ -12,7 +12,7 @@ import { execFile, execSync } from "node:child_process";
|
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
|
|
14
14
|
import { posix } from "path";
|
|
15
|
-
const appCss = "/assets/styles-
|
|
15
|
+
const appCss = "/assets/styles-CWabEzNU.css";
|
|
16
16
|
const swRegisterScript = `
|
|
17
17
|
(() => {
|
|
18
18
|
// Skip PWA service worker inside Capacitor native shell — they conflict
|
|
@@ -372,11 +372,11 @@ const $$splitComponentImporter$2 = () => import("./agents-BuE0Yum3.js");
|
|
|
372
372
|
const Route$t = createFileRoute("/agents")({
|
|
373
373
|
component: lazyRouteComponent($$splitComponentImporter$2, "component")
|
|
374
374
|
});
|
|
375
|
-
const $$splitComponentImporter$1 = () => import("./index-
|
|
375
|
+
const $$splitComponentImporter$1 = () => import("./index-C7lmufwX.js");
|
|
376
376
|
const Route$s = createFileRoute("/")({
|
|
377
377
|
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
378
378
|
});
|
|
379
|
-
const $$splitComponentImporter = () => import("./_sessionKey-
|
|
379
|
+
const $$splitComponentImporter = () => import("./_sessionKey-CaFqmyhU.js").then((n) => n.$);
|
|
380
380
|
const Route$r = createFileRoute("/chat/$sessionKey")({
|
|
381
381
|
component: lazyRouteComponent($$splitComponentImporter, "component")
|
|
382
382
|
});
|
|
@@ -2017,75 +2017,64 @@ const Route$h = createFileRoute("/api/models")({
|
|
|
2017
2017
|
}
|
|
2018
2018
|
}
|
|
2019
2019
|
});
|
|
2020
|
-
const
|
|
2021
|
-
const
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
];
|
|
2020
|
+
const OPENCLAW_GATEWAY_URL = "http://127.0.0.1:18789/v1/chat/completions";
|
|
2021
|
+
const OPENCLAW_MODEL = "openclaw";
|
|
2022
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
2023
|
+
function getGatewayToken() {
|
|
2024
|
+
return process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() || null;
|
|
2025
|
+
}
|
|
2027
2026
|
async function chatCompletion(messages, options) {
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
console.log(`[llm-client] Model ${currentModel} not available, trying next...`);
|
|
2060
|
-
lastError = new Error(`Model ${currentModel} not found`);
|
|
2061
|
-
continue;
|
|
2062
|
-
}
|
|
2063
|
-
throw new Error(`OpenAI API error: ${response.status} ${errorText}`);
|
|
2064
|
-
}
|
|
2065
|
-
const data = await response.json();
|
|
2066
|
-
const content = data.choices[0]?.message?.content?.trim() || "";
|
|
2067
|
-
if (content) {
|
|
2068
|
-
return content;
|
|
2069
|
-
}
|
|
2070
|
-
lastError = new Error(`Model ${currentModel} returned empty response`);
|
|
2071
|
-
continue;
|
|
2072
|
-
} catch (error) {
|
|
2073
|
-
clearTimeout(timeoutId);
|
|
2074
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2075
|
-
lastError = new Error("OpenAI API request timed out");
|
|
2076
|
-
continue;
|
|
2077
|
-
}
|
|
2078
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2079
|
-
continue;
|
|
2027
|
+
const controller = new AbortController();
|
|
2028
|
+
const timeoutId = setTimeout(
|
|
2029
|
+
() => controller.abort(),
|
|
2030
|
+
options?.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
2031
|
+
);
|
|
2032
|
+
try {
|
|
2033
|
+
const token = getGatewayToken();
|
|
2034
|
+
const response = await fetch(OPENCLAW_GATEWAY_URL, {
|
|
2035
|
+
method: "POST",
|
|
2036
|
+
headers: {
|
|
2037
|
+
"Content-Type": "application/json",
|
|
2038
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
2039
|
+
},
|
|
2040
|
+
body: JSON.stringify({
|
|
2041
|
+
model: OPENCLAW_MODEL,
|
|
2042
|
+
messages,
|
|
2043
|
+
max_tokens: options?.maxTokens ?? 200,
|
|
2044
|
+
temperature: options?.temperature ?? 0.7
|
|
2045
|
+
}),
|
|
2046
|
+
signal: controller.signal
|
|
2047
|
+
});
|
|
2048
|
+
if (!response.ok) {
|
|
2049
|
+
const errorText = await response.text().catch(() => "");
|
|
2050
|
+
throw new Error(
|
|
2051
|
+
`OpenClaw Gateway error: ${response.status}${errorText ? ` ${errorText}` : ""}`
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
const data = await response.json();
|
|
2055
|
+
const content = data.choices?.[0]?.message?.content?.trim() || "";
|
|
2056
|
+
if (!content) {
|
|
2057
|
+
throw new Error("OpenClaw Gateway returned empty content");
|
|
2080
2058
|
}
|
|
2059
|
+
return content;
|
|
2060
|
+
} catch (error) {
|
|
2061
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2062
|
+
throw new Error("OpenClaw Gateway request timed out");
|
|
2063
|
+
}
|
|
2064
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
2065
|
+
} finally {
|
|
2066
|
+
clearTimeout(timeoutId);
|
|
2081
2067
|
}
|
|
2082
|
-
throw lastError || new Error("All models failed");
|
|
2083
2068
|
}
|
|
2084
|
-
|
|
2085
|
-
const
|
|
2069
|
+
function parseFollowUps(text) {
|
|
2070
|
+
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/^["']|["']$/g, "").trim()).filter((line) => line.length > 0 && line.length < 150);
|
|
2071
|
+
return lines.slice(0, 3);
|
|
2072
|
+
}
|
|
2073
|
+
async function generateTitleViaOpenclaw(message) {
|
|
2074
|
+
const systemPrompt = `Generate a concise 3-6 word title for this conversation.
|
|
2086
2075
|
Rules:
|
|
2087
2076
|
- No quotes or punctuation at the end
|
|
2088
|
-
- Capture the main topic
|
|
2077
|
+
- Capture the main topic or intent
|
|
2089
2078
|
- Be specific, not generic
|
|
2090
2079
|
- Use title case`;
|
|
2091
2080
|
return chatCompletion(
|
|
@@ -2093,104 +2082,49 @@ Rules:
|
|
|
2093
2082
|
{ role: "system", content: systemPrompt },
|
|
2094
2083
|
{ role: "user", content: message }
|
|
2095
2084
|
],
|
|
2096
|
-
{
|
|
2085
|
+
{ maxTokens: 60, temperature: 0.3 }
|
|
2097
2086
|
);
|
|
2098
2087
|
}
|
|
2099
|
-
async function
|
|
2100
|
-
const
|
|
2088
|
+
async function generateFollowUpsViaOpenclaw(responseText, contextSummary) {
|
|
2089
|
+
const truncatedResponse = responseText.length > 1500 ? `${responseText.slice(0, 1500)}...` : responseText;
|
|
2090
|
+
const trimmedSummary = contextSummary?.slice(0, 500).trim() || "";
|
|
2091
|
+
const userPrompt = trimmedSummary ? `Context: ${trimmedSummary}
|
|
2092
|
+
|
|
2093
|
+
Assistant's response:
|
|
2094
|
+
${truncatedResponse}` : `Assistant's response:
|
|
2095
|
+
${truncatedResponse}`;
|
|
2096
|
+
const systemPrompt = `You are a helpful assistant that generates follow-up question suggestions.
|
|
2097
|
+
Given the assistant's last response, generate exactly 3 short, natural follow-up questions the user might want to ask.
|
|
2098
|
+
|
|
2101
2099
|
Rules:
|
|
2102
|
-
- Each question
|
|
2103
|
-
- Make them
|
|
2104
|
-
- Vary the types: clarification, deeper
|
|
2105
|
-
-
|
|
2100
|
+
- Each suggestion should be a single, concise question (under 60 characters preferred)
|
|
2101
|
+
- Make them contextually relevant to the response
|
|
2102
|
+
- Vary the types: clarification, deeper exploration, practical application
|
|
2103
|
+
- Use natural, conversational language
|
|
2104
|
+
- Do not number them or add any prefix
|
|
2105
|
+
|
|
2106
|
+
Output format: Return ONLY the 3 questions, one per line, nothing else.`;
|
|
2106
2107
|
const response = await chatCompletion(
|
|
2107
2108
|
[
|
|
2108
2109
|
{ role: "system", content: systemPrompt },
|
|
2109
|
-
{ role: "user", content:
|
|
2110
|
+
{ role: "user", content: userPrompt }
|
|
2110
2111
|
],
|
|
2111
|
-
{
|
|
2112
|
+
{ maxTokens: 200, temperature: 0.7 }
|
|
2112
2113
|
);
|
|
2113
|
-
|
|
2114
|
-
let jsonStr = response.trim();
|
|
2115
|
-
if (jsonStr.startsWith("```")) {
|
|
2116
|
-
jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
2117
|
-
}
|
|
2118
|
-
const parsed = JSON.parse(jsonStr);
|
|
2119
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
2120
|
-
return parsed.slice(0, 3).map(String);
|
|
2121
|
-
}
|
|
2122
|
-
} catch {
|
|
2123
|
-
}
|
|
2124
|
-
return [];
|
|
2114
|
+
return parseFollowUps(response);
|
|
2125
2115
|
}
|
|
2126
|
-
async function
|
|
2127
|
-
const llmOptions = typeof options === "string" ? { apiKey: options } : options;
|
|
2116
|
+
async function isOpenclawAvailable() {
|
|
2128
2117
|
try {
|
|
2129
|
-
await chatCompletion(
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2118
|
+
await chatCompletion([{ role: "user", content: "Hi" }], {
|
|
2119
|
+
maxTokens: 1,
|
|
2120
|
+
temperature: 0,
|
|
2121
|
+
timeoutMs: 1e4
|
|
2122
|
+
});
|
|
2133
2123
|
return true;
|
|
2134
2124
|
} catch {
|
|
2135
2125
|
return false;
|
|
2136
2126
|
}
|
|
2137
2127
|
}
|
|
2138
|
-
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
2139
|
-
const PRESET_BASE_URL_ORIGINS = /* @__PURE__ */ new Set([
|
|
2140
|
-
"https://api.openai.com",
|
|
2141
|
-
"https://openrouter.ai",
|
|
2142
|
-
"https://api.kilo.ai",
|
|
2143
|
-
"http://localhost:11434",
|
|
2144
|
-
"http://127.0.0.1:11434"
|
|
2145
|
-
]);
|
|
2146
|
-
function getOrigin(rawBaseUrl) {
|
|
2147
|
-
try {
|
|
2148
|
-
return new URL(rawBaseUrl).origin;
|
|
2149
|
-
} catch {
|
|
2150
|
-
return null;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
function isAllowedClientBaseUrl(rawBaseUrl) {
|
|
2154
|
-
const parsed = new URL(rawBaseUrl);
|
|
2155
|
-
if (!["http:", "https:"].includes(parsed.protocol)) return false;
|
|
2156
|
-
if (parsed.username || parsed.password) return false;
|
|
2157
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
2158
|
-
const isLocalHost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
2159
|
-
if (!isLocalHost && parsed.protocol !== "https:") return false;
|
|
2160
|
-
const origin = parsed.origin;
|
|
2161
|
-
if (PRESET_BASE_URL_ORIGINS.has(origin)) return true;
|
|
2162
|
-
const envBaseUrl = process.env.LLM_BASE_URL?.trim();
|
|
2163
|
-
const envOrigin = envBaseUrl ? getOrigin(envBaseUrl) : null;
|
|
2164
|
-
return Boolean(envOrigin && envOrigin === origin);
|
|
2165
|
-
}
|
|
2166
|
-
function detectProvider(rawBaseUrl) {
|
|
2167
|
-
const baseUrl = rawBaseUrl?.toLowerCase() || "";
|
|
2168
|
-
if (baseUrl.includes("openrouter.ai")) return "openrouter";
|
|
2169
|
-
if (baseUrl.includes("kilo.ai")) return "kilocode";
|
|
2170
|
-
return "openai";
|
|
2171
|
-
}
|
|
2172
|
-
function getLlmConfig(request) {
|
|
2173
|
-
const headerKey = request.headers.get("X-OpenAI-API-Key");
|
|
2174
|
-
const headerBaseUrl = request.headers.get("X-LLM-Base-URL")?.trim() || null;
|
|
2175
|
-
const envBaseUrl = process.env.LLM_BASE_URL?.trim() || null;
|
|
2176
|
-
if (headerBaseUrl) {
|
|
2177
|
-
const origin = getOrigin(headerBaseUrl);
|
|
2178
|
-
if (!origin || !isAllowedClientBaseUrl(headerBaseUrl)) {
|
|
2179
|
-
return {
|
|
2180
|
-
apiKey: null,
|
|
2181
|
-
baseUrl: null,
|
|
2182
|
-
model: null,
|
|
2183
|
-
error: "Disallowed X-LLM-Base-URL value"
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
const baseUrl = headerBaseUrl || envBaseUrl || DEFAULT_OPENAI_BASE_URL;
|
|
2188
|
-
const provider = detectProvider(baseUrl);
|
|
2189
|
-
const envKey = provider === "openrouter" ? process.env.OPENROUTER_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : provider === "kilocode" ? process.env.KILOCODE_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim() : process.env.OPENAI_API_KEY?.trim();
|
|
2190
|
-
const apiKey = headerKey?.trim() || envKey || null;
|
|
2191
|
-
const model = request.headers.get("X-LLM-Model")?.trim() || process.env.LLM_MODEL?.trim() || null;
|
|
2192
|
-
return { apiKey, baseUrl, model, error: null };
|
|
2193
|
-
}
|
|
2194
2128
|
function generateHeuristicTitle(message) {
|
|
2195
2129
|
let text = message.replace(/```[\s\S]*?```/g, " ");
|
|
2196
2130
|
text = text.replace(/`[^`]+`/g, " ");
|
|
@@ -2214,36 +2148,21 @@ function generateHeuristicTitle(message) {
|
|
|
2214
2148
|
const Route$g = createFileRoute("/api/llm-features")({
|
|
2215
2149
|
server: {
|
|
2216
2150
|
handlers: {
|
|
2217
|
-
/**
|
|
2218
|
-
* GET /api/llm-features - Check LLM features status
|
|
2219
|
-
*/
|
|
2220
2151
|
GET: async () => {
|
|
2221
2152
|
try {
|
|
2222
|
-
const
|
|
2223
|
-
const hasOpenRouterKey = Boolean(process.env.OPENROUTER_API_KEY?.trim());
|
|
2224
|
-
const hasKilocodeKey = Boolean(process.env.KILOCODE_API_KEY?.trim());
|
|
2153
|
+
const available = await isOpenclawAvailable();
|
|
2225
2154
|
return json({
|
|
2226
2155
|
ok: true,
|
|
2227
|
-
|
|
2228
|
-
hasOpenRouterKey,
|
|
2229
|
-
hasKilocodeKey
|
|
2156
|
+
available
|
|
2230
2157
|
});
|
|
2231
2158
|
} catch (err) {
|
|
2232
2159
|
return json({
|
|
2233
2160
|
ok: false,
|
|
2234
|
-
|
|
2161
|
+
available: false,
|
|
2235
2162
|
error: err instanceof Error ? err.message : String(err)
|
|
2236
2163
|
});
|
|
2237
2164
|
}
|
|
2238
2165
|
},
|
|
2239
|
-
/**
|
|
2240
|
-
* POST /api/llm-features - Handle LLM feature requests
|
|
2241
|
-
*
|
|
2242
|
-
* Request body should include an "action" field:
|
|
2243
|
-
* - action: "title" - Generate session title
|
|
2244
|
-
* - action: "followups" - Generate follow-up suggestions
|
|
2245
|
-
* - action: "test" - Test API key validity
|
|
2246
|
-
*/
|
|
2247
2166
|
POST: async ({ request }) => {
|
|
2248
2167
|
try {
|
|
2249
2168
|
const body = await request.json().catch(() => ({}));
|
|
@@ -2252,45 +2171,28 @@ const Route$g = createFileRoute("/api/llm-features")({
|
|
|
2252
2171
|
case "title": {
|
|
2253
2172
|
const { message } = body;
|
|
2254
2173
|
if (!message || typeof message !== "string" || message.trim().length < 3) {
|
|
2255
|
-
return json(
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
return json({
|
|
2263
|
-
ok: false,
|
|
2264
|
-
error: llmConfig.error
|
|
2265
|
-
}, { status: 400 });
|
|
2266
|
-
}
|
|
2267
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2268
|
-
const title = generateHeuristicTitle(message);
|
|
2269
|
-
return json({
|
|
2270
|
-
ok: true,
|
|
2271
|
-
title,
|
|
2272
|
-
source: "heuristic"
|
|
2273
|
-
});
|
|
2174
|
+
return json(
|
|
2175
|
+
{
|
|
2176
|
+
ok: false,
|
|
2177
|
+
error: "Message is required and must be at least 3 characters"
|
|
2178
|
+
},
|
|
2179
|
+
{ status: 400 }
|
|
2180
|
+
);
|
|
2274
2181
|
}
|
|
2275
2182
|
try {
|
|
2276
|
-
const title = await
|
|
2277
|
-
apiKey: llmConfig.apiKey || "",
|
|
2278
|
-
...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
|
|
2279
|
-
...llmConfig.model ? { model: llmConfig.model } : {}
|
|
2280
|
-
});
|
|
2183
|
+
const title = await generateTitleViaOpenclaw(message);
|
|
2281
2184
|
return json({
|
|
2282
2185
|
ok: true,
|
|
2283
2186
|
title,
|
|
2284
|
-
source: "
|
|
2187
|
+
source: "openclaw"
|
|
2285
2188
|
});
|
|
2286
2189
|
} catch (err) {
|
|
2287
2190
|
console.error("[llm-features] Title generation error:", err);
|
|
2288
|
-
const title = generateHeuristicTitle(message);
|
|
2289
2191
|
return json({
|
|
2290
2192
|
ok: true,
|
|
2291
|
-
title,
|
|
2193
|
+
title: generateHeuristicTitle(message),
|
|
2292
2194
|
source: "heuristic",
|
|
2293
|
-
error: err instanceof Error ? err.message : "
|
|
2195
|
+
error: err instanceof Error ? err.message : "OpenClaw error, used heuristic"
|
|
2294
2196
|
});
|
|
2295
2197
|
}
|
|
2296
2198
|
}
|
|
@@ -2303,30 +2205,12 @@ const Route$g = createFileRoute("/api/llm-features")({
|
|
|
2303
2205
|
source: "heuristic"
|
|
2304
2206
|
});
|
|
2305
2207
|
}
|
|
2306
|
-
const llmConfig = getLlmConfig(request);
|
|
2307
|
-
if (llmConfig.error) {
|
|
2308
|
-
return json({
|
|
2309
|
-
ok: false,
|
|
2310
|
-
error: llmConfig.error
|
|
2311
|
-
}, { status: 400 });
|
|
2312
|
-
}
|
|
2313
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2314
|
-
return json({
|
|
2315
|
-
ok: true,
|
|
2316
|
-
suggestions: [],
|
|
2317
|
-
source: "heuristic"
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
2208
|
try {
|
|
2321
|
-
const suggestions = await
|
|
2322
|
-
apiKey: llmConfig.apiKey || "",
|
|
2323
|
-
...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
|
|
2324
|
-
...llmConfig.model ? { model: llmConfig.model } : {}
|
|
2325
|
-
});
|
|
2209
|
+
const suggestions = await generateFollowUpsViaOpenclaw(conversationContext);
|
|
2326
2210
|
return json({
|
|
2327
2211
|
ok: true,
|
|
2328
2212
|
suggestions,
|
|
2329
|
-
source: "
|
|
2213
|
+
source: "openclaw"
|
|
2330
2214
|
});
|
|
2331
2215
|
} catch (err) {
|
|
2332
2216
|
console.error("[llm-features] Follow-ups generation error:", err);
|
|
@@ -2334,54 +2218,35 @@ const Route$g = createFileRoute("/api/llm-features")({
|
|
|
2334
2218
|
ok: true,
|
|
2335
2219
|
suggestions: [],
|
|
2336
2220
|
source: "heuristic",
|
|
2337
|
-
error: err instanceof Error ? err.message : "
|
|
2221
|
+
error: err instanceof Error ? err.message : "OpenClaw error"
|
|
2338
2222
|
});
|
|
2339
2223
|
}
|
|
2340
2224
|
}
|
|
2341
2225
|
case "test": {
|
|
2342
|
-
const
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
}, { status: 400 });
|
|
2348
|
-
}
|
|
2349
|
-
if (!llmConfig.apiKey && !llmConfig.baseUrl?.includes("localhost")) {
|
|
2350
|
-
return json({
|
|
2351
|
-
ok: false,
|
|
2352
|
-
error: "API key required (or use Ollama for keyless access)"
|
|
2353
|
-
});
|
|
2354
|
-
}
|
|
2355
|
-
try {
|
|
2356
|
-
const valid = await testApiKey({
|
|
2357
|
-
apiKey: llmConfig.apiKey || "",
|
|
2358
|
-
...llmConfig.baseUrl ? { baseUrl: llmConfig.baseUrl } : {},
|
|
2359
|
-
...llmConfig.model ? { model: llmConfig.model } : {}
|
|
2360
|
-
});
|
|
2361
|
-
return json({
|
|
2362
|
-
ok: true,
|
|
2363
|
-
valid
|
|
2364
|
-
});
|
|
2365
|
-
} catch (err) {
|
|
2366
|
-
return json({
|
|
2367
|
-
ok: true,
|
|
2368
|
-
valid: false,
|
|
2369
|
-
error: err instanceof Error ? err.message : "Test failed"
|
|
2370
|
-
});
|
|
2371
|
-
}
|
|
2226
|
+
const available = await isOpenclawAvailable();
|
|
2227
|
+
return json({
|
|
2228
|
+
ok: true,
|
|
2229
|
+
available
|
|
2230
|
+
});
|
|
2372
2231
|
}
|
|
2373
2232
|
default:
|
|
2374
|
-
return json(
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2233
|
+
return json(
|
|
2234
|
+
{
|
|
2235
|
+
ok: false,
|
|
2236
|
+
error: `Unknown action: ${action}. Valid actions: title, followups, test`
|
|
2237
|
+
},
|
|
2238
|
+
{ status: 400 }
|
|
2239
|
+
);
|
|
2378
2240
|
}
|
|
2379
2241
|
} catch (err) {
|
|
2380
2242
|
console.error("[llm-features] Error:", err);
|
|
2381
|
-
return json(
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2243
|
+
return json(
|
|
2244
|
+
{
|
|
2245
|
+
ok: false,
|
|
2246
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2247
|
+
},
|
|
2248
|
+
{ status: 500 }
|
|
2249
|
+
);
|
|
2385
2250
|
}
|
|
2386
2251
|
}
|
|
2387
2252
|
}
|
|
@@ -2435,21 +2300,6 @@ const Route$f = createFileRoute("/api/history")({
|
|
|
2435
2300
|
}
|
|
2436
2301
|
}
|
|
2437
2302
|
});
|
|
2438
|
-
const FOLLOW_UP_SYSTEM_PROMPT = `You are a helpful assistant that generates follow-up question suggestions.
|
|
2439
|
-
Given the assistant's last response, generate exactly 3 short, natural follow-up questions the user might want to ask.
|
|
2440
|
-
|
|
2441
|
-
Rules:
|
|
2442
|
-
- Each suggestion should be a single, concise question (under 60 characters preferred)
|
|
2443
|
-
- Make them contextually relevant to the response
|
|
2444
|
-
- Vary the types: clarification, deeper exploration, practical application
|
|
2445
|
-
- Use natural, conversational language
|
|
2446
|
-
- Do not number them or add any prefix
|
|
2447
|
-
|
|
2448
|
-
Output format: Return ONLY the 3 questions, one per line, nothing else.`;
|
|
2449
|
-
function parseFollowUps(text) {
|
|
2450
|
-
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\d+[.)\s]+/, "").trim()).map((line) => line.replace(/^[-•*]\s*/, "").trim()).map((line) => line.replace(/^["']|["']$/g, "").trim()).filter((line) => line.length > 0 && line.length < 150);
|
|
2451
|
-
return lines.slice(0, 3);
|
|
2452
|
-
}
|
|
2453
2303
|
const Route$e = createFileRoute("/api/follow-ups")({
|
|
2454
2304
|
server: {
|
|
2455
2305
|
handlers: {
|
|
@@ -2460,24 +2310,11 @@ const Route$e = createFileRoute("/api/follow-ups")({
|
|
|
2460
2310
|
if (!responseText || responseText.length < 30) {
|
|
2461
2311
|
return json({ ok: true, suggestions: [] });
|
|
2462
2312
|
}
|
|
2463
|
-
const
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
${truncatedResponse}` : `Assistant's response:
|
|
2469
|
-
${truncatedResponse}`;
|
|
2470
|
-
const res = await gatewayRpc("chat.complete", {
|
|
2471
|
-
messages: [
|
|
2472
|
-
{ role: "system", content: FOLLOW_UP_SYSTEM_PROMPT },
|
|
2473
|
-
{ role: "user", content: userPrompt }
|
|
2474
|
-
],
|
|
2475
|
-
maxTokens: 200,
|
|
2476
|
-
temperature: 0.7
|
|
2477
|
-
// Use session's default model (no hardcoding!)
|
|
2478
|
-
});
|
|
2479
|
-
const content = res.content || res.message?.content || res.choices?.[0]?.message?.content || "";
|
|
2480
|
-
const suggestions = parseFollowUps(content);
|
|
2313
|
+
const contextSummary = typeof body.contextSummary === "string" ? body.contextSummary : void 0;
|
|
2314
|
+
const suggestions = await generateFollowUpsViaOpenclaw(
|
|
2315
|
+
responseText,
|
|
2316
|
+
contextSummary
|
|
2317
|
+
);
|
|
2481
2318
|
return json({ ok: true, suggestions });
|
|
2482
2319
|
} catch (err) {
|
|
2483
2320
|
console.error("[follow-ups] Error generating suggestions:", err);
|
|
@@ -5,7 +5,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
|
|
|
5
5
|
import { Search01Icon, Cancel01Icon, Loading03Icon } from "@hugeicons/core-free-icons";
|
|
6
6
|
import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-E6cUvMva.js";
|
|
7
7
|
import { useQueryClient } from "@tanstack/react-query";
|
|
8
|
-
import { c as chatQueryKeys } from "./_sessionKey-
|
|
8
|
+
import { c as chatQueryKeys } from "./_sessionKey-CaFqmyhU.js";
|
|
9
9
|
import { c as cn } from "./button-kI8fEIZQ.js";
|
|
10
10
|
import "@base-ui/react/dialog";
|
|
11
11
|
import "zustand";
|
|
@@ -26,7 +26,7 @@ import "remark-gfm";
|
|
|
26
26
|
import "./index-B_F4DTUu.js";
|
|
27
27
|
import "zustand/middleware";
|
|
28
28
|
import "react-dom";
|
|
29
|
-
import "./router-
|
|
29
|
+
import "./router-X2L0PDPI.js";
|
|
30
30
|
import "node:crypto";
|
|
31
31
|
import "node:fs";
|
|
32
32
|
import "node:os";
|