mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat(search): add bing and google support
This commit is contained in:
@@ -74,8 +74,7 @@ CREATE TABLE IF NOT EXISTS search_providers (
|
|||||||
config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
CONSTRAINT search_providers_name_unique UNIQUE (name),
|
CONSTRAINT search_providers_name_unique UNIQUE (name)
|
||||||
CONSTRAINT search_providers_provider_check CHECK (provider IN ('brave'))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS models (
|
CREATE TABLE IF NOT EXISTS models (
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 0015_drop_search_provider_check (down)
|
||||||
|
-- Restore the original CHECK constraint limiting provider to 'brave'.
|
||||||
|
|
||||||
|
ALTER TABLE search_providers ADD CONSTRAINT search_providers_provider_check CHECK (provider IN ('brave'));
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- 0015_drop_search_provider_check
|
||||||
|
-- Remove the CHECK constraint on search_providers.provider so new providers
|
||||||
|
-- can be added without a database migration.
|
||||||
|
|
||||||
|
ALTER TABLE search_providers DROP CONSTRAINT IF EXISTS search_providers_provider_check;
|
||||||
@@ -101,9 +101,19 @@ func (p *Executor) callWebSearch(ctx context.Context, providerName string, confi
|
|||||||
count = 20
|
count = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(providerName) != string(searchproviders.ProviderBrave) {
|
switch strings.TrimSpace(providerName) {
|
||||||
|
case string(searchproviders.ProviderBrave):
|
||||||
|
return p.callBraveSearch(ctx, configJSON, query, count)
|
||||||
|
case string(searchproviders.ProviderBing):
|
||||||
|
return p.callBingSearch(ctx, configJSON, query, count)
|
||||||
|
case string(searchproviders.ProviderGoogle):
|
||||||
|
return p.callGoogleSearch(ctx, configJSON, query, count)
|
||||||
|
default:
|
||||||
return mcpgw.BuildToolErrorResult("unsupported search provider"), nil
|
return mcpgw.BuildToolErrorResult("unsupported search provider"), nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Executor) callBraveSearch(ctx context.Context, configJSON []byte, query string, count int) (map[string]any, error) {
|
||||||
cfg := parseConfig(configJSON)
|
cfg := parseConfig(configJSON)
|
||||||
endpoint := strings.TrimRight(firstNonEmpty(stringValue(cfg["base_url"]), "https://api.search.brave.com/res/v1/web/search"), "/")
|
endpoint := strings.TrimRight(firstNonEmpty(stringValue(cfg["base_url"]), "https://api.search.brave.com/res/v1/web/search"), "/")
|
||||||
reqURL, err := url.Parse(endpoint)
|
reqURL, err := url.Parse(endpoint)
|
||||||
@@ -164,6 +174,134 @@ func (p *Executor) callWebSearch(ctx context.Context, providerName string, confi
|
|||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Executor) callBingSearch(ctx context.Context, configJSON []byte, query string, count int) (map[string]any, error) {
|
||||||
|
cfg := parseConfig(configJSON)
|
||||||
|
endpoint := strings.TrimRight(firstNonEmpty(stringValue(cfg["base_url"]), "https://api.bing.microsoft.com/v7.0/search"), "/")
|
||||||
|
reqURL, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult("invalid search provider base_url"), nil
|
||||||
|
}
|
||||||
|
params := reqURL.Query()
|
||||||
|
params.Set("q", query)
|
||||||
|
params.Set("count", fmt.Sprintf("%d", count))
|
||||||
|
reqURL.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
timeout := parseTimeout(configJSON, 15*time.Second)
|
||||||
|
client := &http.Client{Timeout: timeout}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
apiKey := stringValue(cfg["api_key"])
|
||||||
|
if strings.TrimSpace(apiKey) != "" {
|
||||||
|
req.Header.Set("Ocp-Apim-Subscription-Key", strings.TrimSpace(apiKey))
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return mcpgw.BuildToolErrorResult("search request failed"), nil
|
||||||
|
}
|
||||||
|
var raw struct {
|
||||||
|
WebPages struct {
|
||||||
|
Value []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Snippet string `json:"snippet"`
|
||||||
|
} `json:"value"`
|
||||||
|
} `json:"webPages"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &raw); err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult("invalid search response"), nil
|
||||||
|
}
|
||||||
|
results := make([]map[string]any, 0, len(raw.WebPages.Value))
|
||||||
|
for _, item := range raw.WebPages.Value {
|
||||||
|
results = append(results, map[string]any{
|
||||||
|
"title": item.Name,
|
||||||
|
"url": item.URL,
|
||||||
|
"description": item.Snippet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mcpgw.BuildToolSuccessResult(map[string]any{
|
||||||
|
"query": query,
|
||||||
|
"results": results,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Executor) callGoogleSearch(ctx context.Context, configJSON []byte, query string, count int) (map[string]any, error) {
|
||||||
|
cfg := parseConfig(configJSON)
|
||||||
|
endpoint := strings.TrimRight(firstNonEmpty(stringValue(cfg["base_url"]), "https://customsearch.googleapis.com/customsearch/v1"), "/")
|
||||||
|
reqURL, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult("invalid search provider base_url"), nil
|
||||||
|
}
|
||||||
|
cx := stringValue(cfg["cx"])
|
||||||
|
if cx == "" {
|
||||||
|
return mcpgw.BuildToolErrorResult("Google Custom Search requires cx (Search Engine ID)"), nil
|
||||||
|
}
|
||||||
|
if count > 10 {
|
||||||
|
count = 10
|
||||||
|
}
|
||||||
|
params := reqURL.Query()
|
||||||
|
params.Set("q", query)
|
||||||
|
params.Set("cx", cx)
|
||||||
|
params.Set("num", fmt.Sprintf("%d", count))
|
||||||
|
apiKey := stringValue(cfg["api_key"])
|
||||||
|
if strings.TrimSpace(apiKey) != "" {
|
||||||
|
params.Set("key", strings.TrimSpace(apiKey))
|
||||||
|
}
|
||||||
|
reqURL.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
timeout := parseTimeout(configJSON, 15*time.Second)
|
||||||
|
client := &http.Client{Timeout: timeout}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult(err.Error()), nil
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return mcpgw.BuildToolErrorResult("search request failed"), nil
|
||||||
|
}
|
||||||
|
var raw struct {
|
||||||
|
Items []struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Snippet string `json:"snippet"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &raw); err != nil {
|
||||||
|
return mcpgw.BuildToolErrorResult("invalid search response"), nil
|
||||||
|
}
|
||||||
|
results := make([]map[string]any, 0, len(raw.Items))
|
||||||
|
for _, item := range raw.Items {
|
||||||
|
results = append(results, map[string]any{
|
||||||
|
"title": item.Title,
|
||||||
|
"url": item.Link,
|
||||||
|
"description": item.Snippet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mcpgw.BuildToolSuccessResult(map[string]any{
|
||||||
|
"query": query,
|
||||||
|
"results": results,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseTimeout(configJSON []byte, fallback time.Duration) time.Duration {
|
func parseTimeout(configJSON []byte, fallback time.Duration) time.Duration {
|
||||||
cfg := parseConfig(configJSON)
|
cfg := parseConfig(configJSON)
|
||||||
raw, ok := cfg["timeout_seconds"]
|
raw, ok := cfg["timeout_seconds"]
|
||||||
|
|||||||
@@ -53,6 +53,68 @@ func (s *Service) ListMeta(_ context.Context) []ProviderMeta {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Provider: string(ProviderBing),
|
||||||
|
DisplayName: "Bing",
|
||||||
|
ConfigSchema: ProviderConfigSchema{
|
||||||
|
Fields: map[string]ProviderFieldSchema{
|
||||||
|
"api_key": {
|
||||||
|
Type: "secret",
|
||||||
|
Title: "API Key",
|
||||||
|
Description: "Bing Web Search API subscription key",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"base_url": {
|
||||||
|
Type: "string",
|
||||||
|
Title: "Base URL",
|
||||||
|
Description: "Bing API base URL",
|
||||||
|
Required: false,
|
||||||
|
Example: "https://api.bing.microsoft.com/v7.0/search",
|
||||||
|
},
|
||||||
|
"timeout_seconds": {
|
||||||
|
Type: "number",
|
||||||
|
Title: "Timeout (seconds)",
|
||||||
|
Description: "HTTP timeout in seconds",
|
||||||
|
Required: false,
|
||||||
|
Example: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Provider: string(ProviderGoogle),
|
||||||
|
DisplayName: "Google",
|
||||||
|
ConfigSchema: ProviderConfigSchema{
|
||||||
|
Fields: map[string]ProviderFieldSchema{
|
||||||
|
"api_key": {
|
||||||
|
Type: "secret",
|
||||||
|
Title: "API Key",
|
||||||
|
Description: "Google Custom Search API key",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"cx": {
|
||||||
|
Type: "string",
|
||||||
|
Title: "Search Engine ID",
|
||||||
|
Description: "Google Programmable Search Engine ID (cx)",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"base_url": {
|
||||||
|
Type: "string",
|
||||||
|
Title: "Base URL",
|
||||||
|
Description: "Google Custom Search API base URL",
|
||||||
|
Required: false,
|
||||||
|
Example: "https://customsearch.googleapis.com/customsearch/v1",
|
||||||
|
},
|
||||||
|
"timeout_seconds": {
|
||||||
|
Type: "number",
|
||||||
|
Title: "Timeout (seconds)",
|
||||||
|
Description: "HTTP timeout in seconds",
|
||||||
|
Required: false,
|
||||||
|
Example: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +245,7 @@ func (s *Service) toGetResponse(row sqlc.SearchProvider) GetResponse {
|
|||||||
|
|
||||||
func isValidProviderName(name ProviderName) bool {
|
func isValidProviderName(name ProviderName) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case ProviderBrave:
|
case ProviderBrave, ProviderBing, ProviderGoogle:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import "time"
|
|||||||
type ProviderName string
|
type ProviderName string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProviderBrave ProviderName = "brave"
|
ProviderBrave ProviderName = "brave"
|
||||||
|
ProviderBing ProviderName = "bing"
|
||||||
|
ProviderGoogle ProviderName = "google"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProviderConfigSchema struct {
|
type ProviderConfigSchema struct {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -954,7 +954,7 @@ export type SearchprovidersProviderMeta = {
|
|||||||
provider?: string;
|
provider?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchprovidersProviderName = 'brave';
|
export type SearchprovidersProviderName = 'brave' | 'bing' | 'google';
|
||||||
|
|
||||||
export type SearchprovidersUpdateRequest = {
|
export type SearchprovidersUpdateRequest = {
|
||||||
config?: {
|
config?: {
|
||||||
@@ -3002,6 +3002,42 @@ export type DeleteBotsByBotIdMemoryByIdResponses = {
|
|||||||
|
|
||||||
export type DeleteBotsByBotIdMemoryByIdResponse = DeleteBotsByBotIdMemoryByIdResponses[keyof DeleteBotsByBotIdMemoryByIdResponses];
|
export type DeleteBotsByBotIdMemoryByIdResponse = DeleteBotsByBotIdMemoryByIdResponses[keyof DeleteBotsByBotIdMemoryByIdResponses];
|
||||||
|
|
||||||
|
export type DeleteBotsByBotIdMessagesData = {
|
||||||
|
body?: never;
|
||||||
|
path: {
|
||||||
|
/**
|
||||||
|
* Bot ID
|
||||||
|
*/
|
||||||
|
bot_id: string;
|
||||||
|
};
|
||||||
|
query?: never;
|
||||||
|
url: '/bots/{bot_id}/messages';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteBotsByBotIdMessagesErrors = {
|
||||||
|
/**
|
||||||
|
* Bad Request
|
||||||
|
*/
|
||||||
|
400: HandlersErrorResponse;
|
||||||
|
/**
|
||||||
|
* Forbidden
|
||||||
|
*/
|
||||||
|
403: HandlersErrorResponse;
|
||||||
|
/**
|
||||||
|
* Internal Server Error
|
||||||
|
*/
|
||||||
|
500: HandlersErrorResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteBotsByBotIdMessagesError = DeleteBotsByBotIdMessagesErrors[keyof DeleteBotsByBotIdMessagesErrors];
|
||||||
|
|
||||||
|
export type DeleteBotsByBotIdMessagesResponses = {
|
||||||
|
/**
|
||||||
|
* No Content
|
||||||
|
*/
|
||||||
|
204: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export type GetBotsByBotIdMessagesData = {
|
export type GetBotsByBotIdMessagesData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
const PROVIDER_ICONS: Record<string, [string, string]> = {
|
const PROVIDER_ICONS: Record<string, [string, string]> = {
|
||||||
brave: ['fab', 'brave'],
|
brave: ['fab', 'brave'],
|
||||||
|
bing: ['fab', 'microsoft'],
|
||||||
|
google: ['fab', 'google'],
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_ICON: [string, string] = ['fas', 'globe']
|
const DEFAULT_ICON: [string, string] = ['fas', 'globe']
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import {
|
|||||||
faComments,
|
faComments,
|
||||||
faComment,
|
faComment,
|
||||||
} from '@fortawesome/free-regular-svg-icons'
|
} from '@fortawesome/free-regular-svg-icons'
|
||||||
import { faSlack, faBrave } from '@fortawesome/free-brands-svg-icons'
|
import { faSlack, faBrave, faGoogle, faMicrosoft } from '@fortawesome/free-brands-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faGear,
|
faGear,
|
||||||
@@ -103,6 +103,8 @@ library.add(
|
|||||||
faComment,
|
faComment,
|
||||||
faSlack,
|
faSlack,
|
||||||
faBrave,
|
faBrave,
|
||||||
|
faGoogle,
|
||||||
|
faMicrosoft,
|
||||||
)
|
)
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ import { useI18n } from 'vue-i18n'
|
|||||||
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
|
||||||
import { useDialogMutation } from '@/composables/useDialogMutation'
|
import { useDialogMutation } from '@/composables/useDialogMutation'
|
||||||
|
|
||||||
const PROVIDER_TYPES = ['brave'] as const
|
const PROVIDER_TYPES = ['brave', 'bing', 'google'] as const
|
||||||
|
|
||||||
const open = defineModel<boolean>('open')
|
const open = defineModel<boolean>('open')
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="bing-api-key">API Key</Label>
|
||||||
|
<Input
|
||||||
|
id="bing-api-key"
|
||||||
|
v-model="localConfig.api_key"
|
||||||
|
type="password"
|
||||||
|
aria-label="API Key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="bing-base-url">Base URL</Label>
|
||||||
|
<Input
|
||||||
|
id="bing-base-url"
|
||||||
|
v-model="localConfig.base_url"
|
||||||
|
aria-label="Base URL"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="bing-timeout-seconds">Timeout (seconds)</Label>
|
||||||
|
<Input
|
||||||
|
id="bing-timeout-seconds"
|
||||||
|
v-model.number="localConfig.timeout_seconds"
|
||||||
|
type="number"
|
||||||
|
:min="1"
|
||||||
|
aria-label="Timeout (seconds)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, watch } from 'vue'
|
||||||
|
import { Input, Label } from '@memoh/ui'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: Record<string, unknown>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: Record<string, unknown>]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const localConfig = reactive({
|
||||||
|
api_key: '',
|
||||||
|
base_url: 'https://api.bing.microsoft.com/v7.0/search',
|
||||||
|
timeout_seconds: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
localConfig.api_key = String(val?.api_key ?? '')
|
||||||
|
localConfig.base_url = String(val?.base_url ?? 'https://api.bing.microsoft.com/v7.0/search')
|
||||||
|
const timeout = Number(val?.timeout_seconds ?? 15)
|
||||||
|
localConfig.timeout_seconds = Number.isFinite(timeout) && timeout > 0 ? timeout : 15
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(localConfig, () => {
|
||||||
|
emit('update:modelValue', {
|
||||||
|
api_key: localConfig.api_key,
|
||||||
|
base_url: localConfig.base_url,
|
||||||
|
timeout_seconds: localConfig.timeout_seconds,
|
||||||
|
})
|
||||||
|
}, { deep: true })
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="google-api-key">API Key</Label>
|
||||||
|
<Input
|
||||||
|
id="google-api-key"
|
||||||
|
v-model="localConfig.api_key"
|
||||||
|
type="password"
|
||||||
|
aria-label="API Key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="google-cx">Search Engine ID (cx)</Label>
|
||||||
|
<Input
|
||||||
|
id="google-cx"
|
||||||
|
v-model="localConfig.cx"
|
||||||
|
aria-label="Search Engine ID"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="google-base-url">Base URL</Label>
|
||||||
|
<Input
|
||||||
|
id="google-base-url"
|
||||||
|
v-model="localConfig.base_url"
|
||||||
|
aria-label="Base URL"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="google-timeout-seconds">Timeout (seconds)</Label>
|
||||||
|
<Input
|
||||||
|
id="google-timeout-seconds"
|
||||||
|
v-model.number="localConfig.timeout_seconds"
|
||||||
|
type="number"
|
||||||
|
:min="1"
|
||||||
|
aria-label="Timeout (seconds)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, watch } from 'vue'
|
||||||
|
import { Input, Label } from '@memoh/ui'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: Record<string, unknown>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: Record<string, unknown>]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const localConfig = reactive({
|
||||||
|
api_key: '',
|
||||||
|
cx: '',
|
||||||
|
base_url: 'https://customsearch.googleapis.com/customsearch/v1',
|
||||||
|
timeout_seconds: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
localConfig.api_key = String(val?.api_key ?? '')
|
||||||
|
localConfig.cx = String(val?.cx ?? '')
|
||||||
|
localConfig.base_url = String(val?.base_url ?? 'https://customsearch.googleapis.com/customsearch/v1')
|
||||||
|
const timeout = Number(val?.timeout_seconds ?? 15)
|
||||||
|
localConfig.timeout_seconds = Number.isFinite(timeout) && timeout > 0 ? timeout : 15
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(localConfig, () => {
|
||||||
|
emit('update:modelValue', {
|
||||||
|
api_key: localConfig.api_key,
|
||||||
|
cx: localConfig.cx,
|
||||||
|
base_url: localConfig.base_url,
|
||||||
|
timeout_seconds: localConfig.timeout_seconds,
|
||||||
|
})
|
||||||
|
}, { deep: true })
|
||||||
|
</script>
|
||||||
@@ -79,6 +79,12 @@
|
|||||||
<template v-if="form.values.provider === 'brave'">
|
<template v-if="form.values.provider === 'brave'">
|
||||||
<BraveSettings v-model="configProxy" />
|
<BraveSettings v-model="configProxy" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="form.values.provider === 'bing'">
|
||||||
|
<BingSettings v-model="configProxy" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="form.values.provider === 'google'">
|
||||||
|
<GoogleSettings v-model="configProxy" />
|
||||||
|
</template>
|
||||||
<div
|
<div
|
||||||
v-else-if="form.values.provider"
|
v-else-if="form.values.provider"
|
||||||
class="text-sm text-muted-foreground"
|
class="text-sm text-muted-foreground"
|
||||||
@@ -135,6 +141,8 @@ import {
|
|||||||
} from '@memoh/ui'
|
} from '@memoh/ui'
|
||||||
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
import ConfirmPopover from '@/components/confirm-popover/index.vue'
|
||||||
import BraveSettings from './brave-settings.vue'
|
import BraveSettings from './brave-settings.vue'
|
||||||
|
import BingSettings from './bing-settings.vue'
|
||||||
|
import GoogleSettings from './google-settings.vue'
|
||||||
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
||||||
import { computed, inject, ref, watch } from 'vue'
|
import { computed, inject, ref, watch } from 'vue'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
@@ -144,7 +152,7 @@ import { useMutation, useQueryCache } from '@pinia/colada'
|
|||||||
import { putSearchProvidersById, deleteSearchProvidersById } from '@memoh/sdk'
|
import { putSearchProvidersById, deleteSearchProvidersById } from '@memoh/sdk'
|
||||||
import type { SearchprovidersGetResponse } from '@memoh/sdk'
|
import type { SearchprovidersGetResponse } from '@memoh/sdk'
|
||||||
|
|
||||||
const PROVIDER_TYPES = ['brave'] as const
|
const PROVIDER_TYPES = ['brave', 'bing', 'google'] as const
|
||||||
|
|
||||||
const curProvider = inject('curSearchProvider', ref<SearchprovidersGetResponse>())
|
const curProvider = inject('curSearchProvider', ref<SearchprovidersGetResponse>())
|
||||||
const curProviderId = computed(() => curProvider.value?.id)
|
const curProviderId = computed(() => curProvider.value?.id)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import ProviderSetting from './components/provider-setting.vue'
|
|||||||
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
import SearchProviderLogo from '@/components/search-provider-logo/index.vue'
|
||||||
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
import MasterDetailSidebarLayout from '@/components/master-detail-sidebar-layout/index.vue'
|
||||||
|
|
||||||
const PROVIDER_TYPES = ['brave'] as const
|
const PROVIDER_TYPES = ['brave', 'bing', 'google'] as const
|
||||||
|
|
||||||
const filterProvider = ref('')
|
const filterProvider = ref('')
|
||||||
const { data: providerData } = useQuery({
|
const { data: providerData } = useQuery({
|
||||||
|
|||||||
+48
-2
@@ -2474,6 +2474,48 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Clear all persisted bot-level history messages",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "Delete all bot history messages",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Bot ID",
|
||||||
|
"name": "bot_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/bots/{bot_id}/schedule": {
|
"/bots/{bot_id}/schedule": {
|
||||||
@@ -8173,10 +8215,14 @@ const docTemplate = `{
|
|||||||
"searchproviders.ProviderName": {
|
"searchproviders.ProviderName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"brave"
|
"brave",
|
||||||
|
"bing",
|
||||||
|
"google"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ProviderBrave"
|
"ProviderBrave",
|
||||||
|
"ProviderBing",
|
||||||
|
"ProviderGoogle"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"searchproviders.UpdateRequest": {
|
"searchproviders.UpdateRequest": {
|
||||||
|
|||||||
+48
-2
@@ -2465,6 +2465,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Clear all persisted bot-level history messages",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"messages"
|
||||||
|
],
|
||||||
|
"summary": "Delete all bot history messages",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Bot ID",
|
||||||
|
"name": "bot_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/bots/{bot_id}/schedule": {
|
"/bots/{bot_id}/schedule": {
|
||||||
@@ -8164,10 +8206,14 @@
|
|||||||
"searchproviders.ProviderName": {
|
"searchproviders.ProviderName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"brave"
|
"brave",
|
||||||
|
"bing",
|
||||||
|
"google"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"ProviderBrave"
|
"ProviderBrave",
|
||||||
|
"ProviderBing",
|
||||||
|
"ProviderGoogle"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"searchproviders.UpdateRequest": {
|
"searchproviders.UpdateRequest": {
|
||||||
|
|||||||
@@ -1563,9 +1563,13 @@ definitions:
|
|||||||
searchproviders.ProviderName:
|
searchproviders.ProviderName:
|
||||||
enum:
|
enum:
|
||||||
- brave
|
- brave
|
||||||
|
- bing
|
||||||
|
- google
|
||||||
type: string
|
type: string
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- ProviderBrave
|
- ProviderBrave
|
||||||
|
- ProviderBing
|
||||||
|
- ProviderGoogle
|
||||||
searchproviders.UpdateRequest:
|
searchproviders.UpdateRequest:
|
||||||
properties:
|
properties:
|
||||||
config:
|
config:
|
||||||
@@ -3347,6 +3351,34 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- memory
|
- memory
|
||||||
/bots/{bot_id}/messages:
|
/bots/{bot_id}/messages:
|
||||||
|
delete:
|
||||||
|
description: Clear all persisted bot-level history messages
|
||||||
|
parameters:
|
||||||
|
- description: Bot ID
|
||||||
|
in: path
|
||||||
|
name: bot_id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.ErrorResponse'
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.ErrorResponse'
|
||||||
|
summary: Delete all bot history messages
|
||||||
|
tags:
|
||||||
|
- messages
|
||||||
get:
|
get:
|
||||||
description: List messages for a bot history with optional pagination
|
description: List messages for a bot history with optional pagination
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
Reference in New Issue
Block a user