apteva 0.3.8 → 0.4.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.
@@ -40,6 +40,31 @@ export function CreateAgentModal({
40
40
  }: CreateAgentModalProps) {
41
41
  const { projects, currentProjectId } = useProjects();
42
42
  const selectedProvider = providers.find(p => p.id === form.provider);
43
+ const [ollamaModels, setOllamaModels] = React.useState<Array<{ value: string; label: string }>>([]);
44
+ const [loadingOllamaModels, setLoadingOllamaModels] = React.useState(false);
45
+
46
+ // Fetch Ollama models when Ollama is selected
47
+ React.useEffect(() => {
48
+ if (form.provider === "ollama") {
49
+ setLoadingOllamaModels(true);
50
+ fetch("/api/providers/ollama/models")
51
+ .then(res => res.json())
52
+ .then(data => {
53
+ if (data.models && data.models.length > 0) {
54
+ setOllamaModels(data.models.map((m: { value: string; label?: string }) => ({
55
+ value: m.value,
56
+ label: m.label || m.value,
57
+ })));
58
+ // Auto-select first model if none selected
59
+ if (!form.model && data.models.length > 0) {
60
+ onFormChange({ ...form, model: data.models[0].value });
61
+ }
62
+ }
63
+ })
64
+ .catch(() => setOllamaModels([]))
65
+ .finally(() => setLoadingOllamaModels(false));
66
+ }
67
+ }, [form.provider]);
43
68
 
44
69
  const providerOptions = configuredProviders
45
70
  .filter(p => p.type === "llm")
@@ -48,11 +73,14 @@ export function CreateAgentModal({
48
73
  label: p.name,
49
74
  }));
50
75
 
51
- const modelOptions = selectedProvider?.models.map(m => ({
52
- value: m.value,
53
- label: m.label,
54
- recommended: m.recommended,
55
- })) || [];
76
+ // Use dynamic Ollama models if available, otherwise use provider's default models
77
+ const modelOptions = form.provider === "ollama" && ollamaModels.length > 0
78
+ ? ollamaModels
79
+ : selectedProvider?.models.map(m => ({
80
+ value: m.value,
81
+ label: m.label,
82
+ recommended: m.recommended,
83
+ })) || [];
56
84
 
57
85
  const projectOptions = projects.map(p => ({ value: p.id, label: p.name }));
58
86
 
@@ -158,12 +186,20 @@ export function CreateAgentModal({
158
186
  </FormField>
159
187
 
160
188
  <FormField label="Model">
161
- <Select
162
- value={form.model}
163
- options={modelOptions}
164
- onChange={(value) => onFormChange({ ...form, model: value })}
165
- placeholder="Select model..."
166
- />
189
+ {loadingOllamaModels ? (
190
+ <div className="text-sm text-[#666] py-2">Loading Ollama models...</div>
191
+ ) : form.provider === "ollama" && modelOptions.length === 0 ? (
192
+ <div className="text-sm text-yellow-400/80 py-2">
193
+ No models found. Run <code className="bg-[#1a1a1a] px-1 rounded">ollama pull llama3.3</code> to download a model.
194
+ </div>
195
+ ) : (
196
+ <Select
197
+ value={form.model}
198
+ options={modelOptions}
199
+ onChange={(value) => onFormChange({ ...form, model: value })}
200
+ placeholder="Select model..."
201
+ />
202
+ )}
167
203
  </FormField>
168
204
 
169
205
  <FormField label="System Prompt">
@@ -793,6 +793,19 @@ function ProviderKeyCard({
793
793
  onSave,
794
794
  onDelete,
795
795
  }: ProviderKeyCardProps) {
796
+ const isOllama = provider.id === "ollama";
797
+ const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
798
+
799
+ // Check Ollama status when configured
800
+ React.useEffect(() => {
801
+ if (isOllama && provider.hasKey) {
802
+ fetch("/api/providers/ollama/status")
803
+ .then(res => res.json())
804
+ .then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount }))
805
+ .catch(() => setOllamaStatus({ connected: false }));
806
+ }
807
+ }, [isOllama, provider.hasKey]);
808
+
796
809
  return (
797
810
  <div className={`bg-[#111] border rounded-lg p-4 ${
798
811
  provider.hasKey ? 'border-green-500/20' : 'border-[#1a1a1a]'
@@ -803,13 +816,28 @@ function ProviderKeyCard({
803
816
  <p className="text-sm text-[#666] truncate">
804
817
  {provider.type === "integration"
805
818
  ? (provider.description || "MCP integration")
806
- : `${provider.models.length} models`}
819
+ : isOllama
820
+ ? "Run models locally"
821
+ : `${provider.models.length} models`}
807
822
  </p>
808
823
  </div>
809
824
  {provider.hasKey ? (
810
- <span className="text-green-400 text-xs flex items-center gap-1 bg-green-500/10 px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
811
- <CheckIcon className="w-3 h-3" />
812
- {provider.keyHint}
825
+ <span className={`text-xs flex items-center gap-1 px-2 py-1 rounded whitespace-nowrap flex-shrink-0 ${
826
+ isOllama && ollamaStatus
827
+ ? ollamaStatus.connected
828
+ ? "text-green-400 bg-green-500/10"
829
+ : "text-yellow-400 bg-yellow-500/10"
830
+ : "text-green-400 bg-green-500/10"
831
+ }`}>
832
+ {isOllama && ollamaStatus ? (
833
+ ollamaStatus.connected ? (
834
+ <><CheckIcon className="w-3 h-3" />{ollamaStatus.modelCount} models</>
835
+ ) : (
836
+ <>Not running</>
837
+ )
838
+ ) : (
839
+ <><CheckIcon className="w-3 h-3" />{provider.keyHint}</>
840
+ )}
813
841
  </span>
814
842
  ) : (
815
843
  <span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
@@ -822,13 +850,20 @@ function ProviderKeyCard({
822
850
  {isEditing ? (
823
851
  <div className="space-y-3">
824
852
  <input
825
- type="password"
853
+ type={isOllama ? "text" : "password"}
826
854
  value={apiKey}
827
855
  onChange={e => onApiKeyChange(e.target.value)}
828
- placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
856
+ placeholder={isOllama
857
+ ? "http://localhost:11434"
858
+ : provider.hasKey ? "Enter new API key..." : "Enter API key..."}
829
859
  autoFocus
830
860
  className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
831
861
  />
862
+ {isOllama && (
863
+ <p className="text-xs text-[#666]">
864
+ Enter your Ollama server URL. Default is http://localhost:11434
865
+ </p>
866
+ )}
832
867
  {error && <p className="text-red-400 text-sm">{error}</p>}
833
868
  {success && <p className="text-green-400 text-sm">{success}</p>}
834
869
  <div className="flex gap-2">
@@ -843,7 +878,7 @@ function ProviderKeyCard({
843
878
  disabled={!apiKey || saving}
844
879
  className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
845
880
  >
846
- {testing ? "Validating..." : saving ? "Saving..." : "Save"}
881
+ {testing ? "Validating..." : saving ? "Saving..." : isOllama ? "Connect" : "Save"}
847
882
  </button>
848
883
  </div>
849
884
  </div>
@@ -855,14 +890,14 @@ function ProviderKeyCard({
855
890
  rel="noopener noreferrer"
856
891
  className="text-sm text-[#3b82f6] hover:underline"
857
892
  >
858
- View docs
893
+ {isOllama ? "Download Ollama" : "View docs"}
859
894
  </a>
860
895
  <div className="flex items-center gap-3">
861
896
  <button
862
897
  onClick={onStartEdit}
863
898
  className="text-sm text-[#888] hover:text-[#e0e0e0]"
864
899
  >
865
- Update key
900
+ {isOllama ? "Change URL" : "Update key"}
866
901
  </button>
867
902
  <button
868
903
  onClick={onDelete}
@@ -880,13 +915,13 @@ function ProviderKeyCard({
880
915
  rel="noopener noreferrer"
881
916
  className="text-sm text-[#3b82f6] hover:underline"
882
917
  >
883
- Get API key
918
+ {isOllama ? "Download Ollama" : "Get API key"}
884
919
  </a>
885
920
  <button
886
921
  onClick={onStartEdit}
887
922
  className="text-sm text-[#f97316] hover:text-[#fb923c]"
888
923
  >
889
- + Add key
924
+ {isOllama ? "Configure" : "+ Add key"}
890
925
  </button>
891
926
  </div>
892
927
  )}