mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-21 21:31:12 +02:00
parent
cccb7a8a65
commit
9df1bb6765
4 changed files with 166 additions and 86 deletions
|
|
@ -57,14 +57,14 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
|
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
|
||||||
const [modelsLoading, setModelsLoading] = useState(false)
|
const [modelsLoading, setModelsLoading] = useState(false)
|
||||||
const [modelsError, setModelsError] = useState<string | null>(null)
|
const [modelsError, setModelsError] = useState<string | null>(null)
|
||||||
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string }>>({
|
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>>({
|
||||||
openai: { apiKey: "", baseURL: "", model: "" },
|
openai: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
anthropic: { apiKey: "", baseURL: "", model: "" },
|
anthropic: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
google: { apiKey: "", baseURL: "", model: "" },
|
google: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
openrouter: { apiKey: "", baseURL: "", model: "" },
|
openrouter: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
aigateway: { apiKey: "", baseURL: "", model: "" },
|
aigateway: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "" },
|
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "", knowledgeGraphModel: "" },
|
||||||
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "" },
|
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "", knowledgeGraphModel: "" },
|
||||||
})
|
})
|
||||||
const [testState, setTestState] = useState<{ status: "idle" | "testing" | "success" | "error"; error?: string }>({
|
const [testState, setTestState] = useState<{ status: "idle" | "testing" | "success" | "error"; error?: string }>({
|
||||||
status: "idle",
|
status: "idle",
|
||||||
|
|
@ -87,7 +87,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
const [slackConnecting, setSlackConnecting] = useState(false)
|
const [slackConnecting, setSlackConnecting] = useState(false)
|
||||||
|
|
||||||
const updateProviderConfig = useCallback(
|
const updateProviderConfig = useCallback(
|
||||||
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string }>) => {
|
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
|
||||||
setProviderConfigs(prev => ({
|
setProviderConfigs(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[provider]: { ...prev[provider], ...updates },
|
[provider]: { ...prev[provider], ...updates },
|
||||||
|
|
@ -287,6 +287,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
const apiKey = activeConfig.apiKey.trim() || undefined
|
const apiKey = activeConfig.apiKey.trim() || undefined
|
||||||
const baseURL = activeConfig.baseURL.trim() || undefined
|
const baseURL = activeConfig.baseURL.trim() || undefined
|
||||||
const model = activeConfig.model.trim()
|
const model = activeConfig.model.trim()
|
||||||
|
const knowledgeGraphModel = activeConfig.knowledgeGraphModel.trim() || undefined
|
||||||
const providerConfig = {
|
const providerConfig = {
|
||||||
provider: {
|
provider: {
|
||||||
flavor: llmProvider,
|
flavor: llmProvider,
|
||||||
|
|
@ -294,6 +295,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
baseURL,
|
baseURL,
|
||||||
},
|
},
|
||||||
model,
|
model,
|
||||||
|
knowledgeGraphModel,
|
||||||
}
|
}
|
||||||
const result = await window.ipc.invoke("models:test", providerConfig)
|
const result = await window.ipc.invoke("models:test", providerConfig)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
@ -657,39 +659,74 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Model</span>
|
<div className="space-y-2">
|
||||||
{modelsLoading ? (
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Assistant model</span>
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
{modelsLoading ? (
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
Loading models...
|
<Loader2 className="size-4 animate-spin" />
|
||||||
</div>
|
Loading...
|
||||||
) : showModelInput ? (
|
</div>
|
||||||
<Input
|
) : showModelInput ? (
|
||||||
value={activeConfig.model}
|
<Input
|
||||||
onChange={(e) => updateProviderConfig(llmProvider, { model: e.target.value })}
|
value={activeConfig.model}
|
||||||
placeholder="Enter model"
|
onChange={(e) => updateProviderConfig(llmProvider, { model: e.target.value })}
|
||||||
/>
|
placeholder="Enter model"
|
||||||
) : (
|
/>
|
||||||
<Select
|
) : (
|
||||||
value={activeConfig.model}
|
<Select
|
||||||
onValueChange={(value) => updateProviderConfig(llmProvider, { model: value })}
|
value={activeConfig.model}
|
||||||
>
|
onValueChange={(value) => updateProviderConfig(llmProvider, { model: value })}
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Select a model" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a model" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
{modelsForProvider.map((model) => (
|
<SelectContent>
|
||||||
<SelectItem key={model.id} value={model.id}>
|
{modelsForProvider.map((model) => (
|
||||||
{model.name || model.id}
|
<SelectItem key={model.id} value={model.id}>
|
||||||
</SelectItem>
|
{model.name || model.id}
|
||||||
))}
|
</SelectItem>
|
||||||
</SelectContent>
|
))}
|
||||||
</Select>
|
</SelectContent>
|
||||||
)}
|
</Select>
|
||||||
{modelsError && (
|
)}
|
||||||
<div className="text-xs text-destructive">{modelsError}</div>
|
{modelsError && (
|
||||||
)}
|
<div className="text-xs text-destructive">{modelsError}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Knowledge graph model</span>
|
||||||
|
{modelsLoading ? (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : showModelInput ? (
|
||||||
|
<Input
|
||||||
|
value={activeConfig.knowledgeGraphModel}
|
||||||
|
onChange={(e) => updateProviderConfig(llmProvider, { knowledgeGraphModel: e.target.value })}
|
||||||
|
placeholder={activeConfig.model || "Enter model"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
value={activeConfig.knowledgeGraphModel || "__same__"}
|
||||||
|
onValueChange={(value) => updateProviderConfig(llmProvider, { knowledgeGraphModel: value === "__same__" ? "" : value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a model" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__same__">Same as assistant</SelectItem>
|
||||||
|
{modelsForProvider.map((model) => (
|
||||||
|
<SelectItem key={model.id} value={model.id}>
|
||||||
|
{model.name || model.id}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showApiKey && (
|
{showApiKey && (
|
||||||
|
|
|
||||||
|
|
@ -167,14 +167,14 @@ const defaultBaseURLs: Partial<Record<LlmProviderFlavor, string>> = {
|
||||||
|
|
||||||
function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
||||||
const [provider, setProvider] = useState<LlmProviderFlavor>("openai")
|
const [provider, setProvider] = useState<LlmProviderFlavor>("openai")
|
||||||
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string }>>({
|
const [providerConfigs, setProviderConfigs] = useState<Record<LlmProviderFlavor, { apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>>({
|
||||||
openai: { apiKey: "", baseURL: "", model: "" },
|
openai: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
anthropic: { apiKey: "", baseURL: "", model: "" },
|
anthropic: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
google: { apiKey: "", baseURL: "", model: "" },
|
google: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
openrouter: { apiKey: "", baseURL: "", model: "" },
|
openrouter: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
aigateway: { apiKey: "", baseURL: "", model: "" },
|
aigateway: { apiKey: "", baseURL: "", model: "", knowledgeGraphModel: "" },
|
||||||
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "" },
|
ollama: { apiKey: "", baseURL: "http://localhost:11434", model: "", knowledgeGraphModel: "" },
|
||||||
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "" },
|
"openai-compatible": { apiKey: "", baseURL: "http://localhost:1234/v1", model: "", knowledgeGraphModel: "" },
|
||||||
})
|
})
|
||||||
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
|
const [modelsCatalog, setModelsCatalog] = useState<Record<string, LlmModelOption[]>>({})
|
||||||
const [modelsLoading, setModelsLoading] = useState(false)
|
const [modelsLoading, setModelsLoading] = useState(false)
|
||||||
|
|
@ -199,7 +199,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
||||||
(!requiresBaseURL || activeConfig.baseURL.trim().length > 0)
|
(!requiresBaseURL || activeConfig.baseURL.trim().length > 0)
|
||||||
|
|
||||||
const updateConfig = useCallback(
|
const updateConfig = useCallback(
|
||||||
(prov: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string }>) => {
|
(prov: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
|
||||||
setProviderConfigs(prev => ({
|
setProviderConfigs(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[prov]: { ...prev[prov], ...updates },
|
[prov]: { ...prev[prov], ...updates },
|
||||||
|
|
@ -229,6 +229,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
||||||
apiKey: parsed.provider.apiKey || "",
|
apiKey: parsed.provider.apiKey || "",
|
||||||
baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""),
|
baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""),
|
||||||
model: parsed.model,
|
model: parsed.model,
|
||||||
|
knowledgeGraphModel: parsed.knowledgeGraphModel || "",
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -296,6 +297,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
||||||
baseURL: activeConfig.baseURL.trim() || undefined,
|
baseURL: activeConfig.baseURL.trim() || undefined,
|
||||||
},
|
},
|
||||||
model: activeConfig.model.trim(),
|
model: activeConfig.model.trim(),
|
||||||
|
knowledgeGraphModel: activeConfig.knowledgeGraphModel.trim() || undefined,
|
||||||
}
|
}
|
||||||
const result = await window.ipc.invoke("models:test", providerConfig)
|
const result = await window.ipc.invoke("models:test", providerConfig)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
@ -362,40 +364,75 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Model selection */}
|
{/* Model selection - side by side */}
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Model</span>
|
<div className="space-y-2">
|
||||||
{modelsLoading ? (
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Assistant model</span>
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
{modelsLoading ? (
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
Loading models...
|
<Loader2 className="size-4 animate-spin" />
|
||||||
</div>
|
Loading...
|
||||||
) : showModelInput ? (
|
</div>
|
||||||
<Input
|
) : showModelInput ? (
|
||||||
value={activeConfig.model}
|
<Input
|
||||||
onChange={(e) => updateConfig(provider, { model: e.target.value })}
|
value={activeConfig.model}
|
||||||
placeholder="Enter model"
|
onChange={(e) => updateConfig(provider, { model: e.target.value })}
|
||||||
/>
|
placeholder="Enter model"
|
||||||
) : (
|
/>
|
||||||
<Select
|
) : (
|
||||||
value={activeConfig.model}
|
<Select
|
||||||
onValueChange={(value) => updateConfig(provider, { model: value })}
|
value={activeConfig.model}
|
||||||
>
|
onValueChange={(value) => updateConfig(provider, { model: value })}
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Select a model" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a model" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
{modelsForProvider.map((model) => (
|
<SelectContent>
|
||||||
<SelectItem key={model.id} value={model.id}>
|
{modelsForProvider.map((model) => (
|
||||||
{model.name || model.id}
|
<SelectItem key={model.id} value={model.id}>
|
||||||
</SelectItem>
|
{model.name || model.id}
|
||||||
))}
|
</SelectItem>
|
||||||
</SelectContent>
|
))}
|
||||||
</Select>
|
</SelectContent>
|
||||||
)}
|
</Select>
|
||||||
{modelsError && (
|
)}
|
||||||
<div className="text-xs text-destructive">{modelsError}</div>
|
{modelsError && (
|
||||||
)}
|
<div className="text-xs text-destructive">{modelsError}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Knowledge graph model</span>
|
||||||
|
{modelsLoading ? (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
) : showModelInput ? (
|
||||||
|
<Input
|
||||||
|
value={activeConfig.knowledgeGraphModel}
|
||||||
|
onChange={(e) => updateConfig(provider, { knowledgeGraphModel: e.target.value })}
|
||||||
|
placeholder={activeConfig.model || "Enter model"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
value={activeConfig.knowledgeGraphModel || "__same__"}
|
||||||
|
onValueChange={(value) => updateConfig(provider, { knowledgeGraphModel: value === "__same__" ? "" : value })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a model" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__same__">Same as assistant</SelectItem>
|
||||||
|
{modelsForProvider.map((model) => (
|
||||||
|
<SelectItem key={model.id} value={model.id}>
|
||||||
|
{model.name || model.id}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* API Key */}
|
{/* API Key */}
|
||||||
|
|
|
||||||
|
|
@ -706,7 +706,12 @@ export async function* streamAgent({
|
||||||
|
|
||||||
// set up provider + model
|
// set up provider + model
|
||||||
const provider = createProvider(modelConfig.provider);
|
const provider = createProvider(modelConfig.provider);
|
||||||
const model = provider.languageModel(modelConfig.model);
|
const knowledgeGraphAgents = ["note_creation", "email-draft", "meeting-prep"];
|
||||||
|
const modelId = (knowledgeGraphAgents.includes(state.agentName!) && modelConfig.knowledgeGraphModel)
|
||||||
|
? modelConfig.knowledgeGraphModel
|
||||||
|
: modelConfig.model;
|
||||||
|
const model = provider.languageModel(modelId);
|
||||||
|
logger.log(`using model: ${modelId}`);
|
||||||
|
|
||||||
let loopCounter = 0;
|
let loopCounter = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ export const LlmProvider = z.object({
|
||||||
export const LlmModelConfig = z.object({
|
export const LlmModelConfig = z.object({
|
||||||
provider: LlmProvider,
|
provider: LlmProvider,
|
||||||
model: z.string(),
|
model: z.string(),
|
||||||
|
knowledgeGraphModel: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue