@open-mercato/search 0.6.7 → 0.6.8-develop.6874.1.982d6097d8
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +7 -1
- package/dist/lib/presenter-enricher.js +12 -12
- package/dist/lib/presenter-enricher.js.map +2 -2
- package/dist/modules/search/ai-tools.js +5 -2
- package/dist/modules/search/ai-tools.js.map +2 -2
- package/dist/modules/search/api/openapi.js +2 -0
- package/dist/modules/search/api/openapi.js.map +2 -2
- package/dist/modules/search/api/settings/vector-store/route.js +34 -0
- package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
- package/dist/modules/search/events.js +13 -0
- package/dist/modules/search/events.js.map +7 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js +2 -9
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
- package/dist/modules/search/frontend/components/HybridSearchTable.js +2 -11
- package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
- package/dist/modules/search/frontend/components/TopbarSearchInline.js +2 -9
- package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +15 -3
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
- package/dist/modules/search/frontend/lib/entityTypeLabel.js +19 -0
- package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +7 -0
- package/dist/modules/search/i18n/de.json +49 -0
- package/dist/modules/search/i18n/en.json +49 -0
- package/dist/modules/search/i18n/es.json +49 -0
- package/dist/modules/search/i18n/ko.json +239 -0
- package/dist/modules/search/i18n/pl.json +49 -0
- package/dist/modules/search/lib/auto-indexing.js +7 -8
- package/dist/modules/search/lib/auto-indexing.js.map +2 -2
- package/dist/service.js +2 -5
- package/dist/service.js.map +2 -2
- package/dist/strategies/vector.strategy.js +3 -1
- package/dist/strategies/vector.strategy.js.map +2 -2
- package/dist/vector/drivers/pgvector/index.js +55 -1
- package/dist/vector/drivers/pgvector/index.js.map +2 -2
- package/dist/vector/types.js.map +2 -2
- package/package.json +11 -10
- package/src/__tests__/pgvector-extension-availability.test.ts +158 -0
- package/src/__tests__/presenter-enricher.test.ts +48 -0
- package/src/__tests__/presenter-locale.test.ts +76 -0
- package/src/lib/presenter-enricher.ts +14 -14
- package/src/modules/search/README.md +4 -1
- package/src/modules/search/ai-tools.ts +5 -2
- package/src/modules/search/api/__tests__/global-search.routes.test.ts +167 -0
- package/src/modules/search/api/openapi.ts +2 -0
- package/src/modules/search/api/settings/vector-store/route.ts +43 -0
- package/src/modules/search/events.ts +10 -0
- package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +2 -15
- package/src/modules/search/frontend/components/HybridSearchTable.tsx +2 -17
- package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +2 -0
- package/src/modules/search/frontend/components/TopbarSearchInline.tsx +2 -15
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +32 -2
- package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +36 -1
- package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +25 -0
- package/src/modules/search/frontend/lib/entityTypeLabel.ts +22 -0
- package/src/modules/search/i18n/de.json +49 -0
- package/src/modules/search/i18n/en.json +49 -0
- package/src/modules/search/i18n/es.json +49 -0
- package/src/modules/search/i18n/ko.json +239 -0
- package/src/modules/search/i18n/pl.json +49 -0
- package/src/modules/search/lib/auto-indexing.ts +7 -12
- package/src/service.ts +2 -7
- package/src/strategies/vector.strategy.ts +7 -1
- package/src/vector/drivers/pgvector/index.ts +77 -1
- package/src/vector/types.ts +14 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
{
|
|
2
|
+
"search.api.errors.autoIndexingDisabled": "OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING(레거시 별칭: DISABLE_VECTOR_SEARCH_AUTOINDEXING)에 의해 자동 인덱싱이 비활성화되어 있습니다.",
|
|
3
|
+
"search.api.errors.configUnavailable": "구성 서비스를 사용할 수 없습니다.",
|
|
4
|
+
"search.api.errors.confirmAllRequired": "모든 항목에 대한 확인이 필요합니다.",
|
|
5
|
+
"search.api.errors.indexFetchFailed": "인덱스 데이터를 가져오지 못했습니다.",
|
|
6
|
+
"search.api.errors.indexUnavailable": "검색 인덱스를 사용할 수 없습니다.",
|
|
7
|
+
"search.api.errors.indexerUnavailable": "검색 인덱서를 사용할 수 없습니다.",
|
|
8
|
+
"search.api.errors.invalidOllamaBaseUrl": "Ollama 기본 URL이 허용되지 않습니다. 환경 변수에 OLLAMA_BASE_URL을 설정하거나 OM_SEARCH_OLLAMA_BASE_URL_ALLOWLIST에 호스트를 추가하세요.",
|
|
9
|
+
"search.api.errors.invalidProvider": "잘못된 임베딩 제공업체입니다.",
|
|
10
|
+
"search.api.errors.invalidStrategies": "지정된 검색 전략이 잘못되었습니다.",
|
|
11
|
+
"search.api.errors.lockFailed": "인덱스 잠금을 획득하지 못했습니다.",
|
|
12
|
+
"search.api.errors.missingQuery": "검색어는 필수입니다.",
|
|
13
|
+
"search.api.errors.noIndexableStrategy": "사용 가능한 인덱싱 전략이 없습니다.",
|
|
14
|
+
"search.api.errors.providerNotConfigured": "제공업체가 구성되지 않았습니다. 필요한 환경 변수를 설정하세요.",
|
|
15
|
+
"search.api.errors.providerUnavailable": "선택한 제공자를 사용할 수 없습니다.",
|
|
16
|
+
"search.api.errors.purgeFailed": "검색 인덱스 삭제에 실패했습니다.",
|
|
17
|
+
"search.api.errors.recreateFailed": "새 차원으로 벡터 테이블을 재생성하지 못했습니다.",
|
|
18
|
+
"search.api.errors.reindexFailed": "재인덱싱 작업에 실패했습니다.",
|
|
19
|
+
"search.api.errors.reindexInProgress": "이미 재인덱싱이 진행 중입니다.",
|
|
20
|
+
"search.api.errors.scopedQueueCancelFailed": "대기 중인 재인덱싱 작업 취소에 실패했습니다.",
|
|
21
|
+
"search.api.errors.scopedQueueCancelUnavailable": "범위 지정 대기열 취소를 사용할 수 없습니다.",
|
|
22
|
+
"search.api.errors.searchFailed": "검색 작업에 실패했습니다.",
|
|
23
|
+
"search.api.errors.serviceUnavailable": "검색 서비스를 사용할 수 없습니다.",
|
|
24
|
+
"search.api.errors.strategyUnavailable": "요청한 검색 전략을 사용할 수 없습니다.",
|
|
25
|
+
"search.api.errors.updateFailed": "검색 설정 업데이트에 실패했습니다.",
|
|
26
|
+
"search.api.errors.vectorUnavailable": "벡터 검색을 사용할 수 없습니다.",
|
|
27
|
+
"search.config.nav.hybridSearch": "검색 설정",
|
|
28
|
+
"search.dialog.actions.cancel": "취소 (Esc)",
|
|
29
|
+
"search.dialog.actions.clear": "검색 지우기",
|
|
30
|
+
"search.dialog.actions.openGlobalSearch": "전역 검색 열기",
|
|
31
|
+
"search.dialog.actions.openSelected": "열기 (Enter)",
|
|
32
|
+
"search.dialog.actions.search": "검색",
|
|
33
|
+
"search.dialog.empty.hint": "인덱싱된 레코드를 검색하려면 최소 {{count}}자를 입력하세요.",
|
|
34
|
+
"search.dialog.empty.none": "결과를 찾을 수 없습니다.",
|
|
35
|
+
"search.dialog.errors.noPermission": "전역 검색을 사용할 권한이 없습니다.",
|
|
36
|
+
"search.dialog.errors.searchFailed": "검색에 실패했습니다.",
|
|
37
|
+
"search.dialog.input.placeholder": "고객, 메모, 딜, 할 일 검색...",
|
|
38
|
+
"search.dialog.instructions": "인덱싱된 레코드를 검색하려면 입력하세요. 화살표 키로 결과를 탐색하세요.",
|
|
39
|
+
"search.dialog.noLink": "링크 없음",
|
|
40
|
+
"search.dialog.noLinkHint": "이 엔터티 유형에 대한 search.ts 구성이 없습니다",
|
|
41
|
+
"search.dialog.score.title": "관련성 점수",
|
|
42
|
+
"search.dialog.shortcuts.hint": "Cmd+K를 눌러 전환하세요. Enter로 엽니다",
|
|
43
|
+
"search.dialog.strategies.exact": "정확히 일치",
|
|
44
|
+
"search.dialog.strategies.exactDescription": "정확한 키워드 매칭",
|
|
45
|
+
"search.dialog.strategies.fuzzy": "유사 검색",
|
|
46
|
+
"search.dialog.strategies.fuzzyDescription": "오타에 강한 전체 텍스트 검색",
|
|
47
|
+
"search.dialog.strategies.label": "검색 방식:",
|
|
48
|
+
"search.dialog.strategies.semantic": "의미 기반",
|
|
49
|
+
"search.dialog.strategies.semanticDescription": "AI 기반 의미 기반 검색",
|
|
50
|
+
"search.dialog.strategies.toggle": "검색 옵션",
|
|
51
|
+
"search.dialog.title": "전역 검색",
|
|
52
|
+
"search.dialog.warnings.configureLink": "검색 설정 구성",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "권하다",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "옵션 템플릿",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "가격 유형",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "제품",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "범주",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "꼬리표",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "단위 변환",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "변종",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "고객 역할",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "고객 사용자",
|
|
63
|
+
"search.entityType.customers.customer_activity": "활동",
|
|
64
|
+
"search.entityType.customers.customer_comment": "논평",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "회사",
|
|
66
|
+
"search.entityType.customers.customer_deal": "거래",
|
|
67
|
+
"search.entityType.customers.customer_entity": "고객",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "사람",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "일",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "받은 편지함 제안",
|
|
71
|
+
"search.entityType.messages.message": "메시지",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "가용성 규칙",
|
|
73
|
+
"search.entityType.resources.resources_resource": "의지",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "리소스 유형",
|
|
75
|
+
"search.entityType.sales.sales_channel": "채널",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "신용 메모",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "대변 메모 라인",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "배송 기간",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "문서 주소",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "문서 태그",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "송장",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "송장 라인",
|
|
83
|
+
"search.entityType.sales.sales_note": "메모",
|
|
84
|
+
"search.entityType.sales.sales_order": "주문하다",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "주문 조정",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "주문 라인",
|
|
87
|
+
"search.entityType.sales.sales_payment": "지불",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "지불 할당",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "결제수단",
|
|
90
|
+
"search.entityType.sales.sales_quote": "인용하다",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "견적 조정",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "견적 라인",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "선적",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "배송 품목",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "배송 방법",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "세율",
|
|
97
|
+
"search.entityType.staff.staff_team": "팀",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "팀원",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "팀 역할",
|
|
100
|
+
"search.messages.missingConfig": "의미 기반 검색을 사용하려면 임베딩 제공업체를 구성해야 합니다.",
|
|
101
|
+
"search.nav.hybridSearch": "검색",
|
|
102
|
+
"search.scopeHint.currentOrg": "현재 조직으로 범위 지정됨",
|
|
103
|
+
"search.settings.actions.apply": "적용",
|
|
104
|
+
"search.settings.actions.cancel": "취소",
|
|
105
|
+
"search.settings.actions.confirm": "확인",
|
|
106
|
+
"search.settings.actions.loading": "불러오는 중...",
|
|
107
|
+
"search.settings.actions.refresh": "새로고침",
|
|
108
|
+
"search.settings.activity.noLogs": "아직 활동 로그가 없습니다.",
|
|
109
|
+
"search.settings.autoIndexing.description": "새로 생성되거나 업데이트된 레코드를 벡터 검색을 위해 자동으로 인덱싱합니다.",
|
|
110
|
+
"search.settings.autoIndexing.label": "자동 인덱싱 활성화",
|
|
111
|
+
"search.settings.autoIndexing.locked": "환경 변수를 통해 비활성화되었습니다.",
|
|
112
|
+
"search.settings.badge.ready": "준비됨",
|
|
113
|
+
"search.settings.badge.setup": "설정 필요",
|
|
114
|
+
"search.settings.cancelLabel": "취소",
|
|
115
|
+
"search.settings.change.bullet1": "기존 벡터 데이터가 지워집니다",
|
|
116
|
+
"search.settings.change.bullet2": "재인덱싱하는 동안 벡터 검색을 사용할 수 없습니다",
|
|
117
|
+
"search.settings.change.description": "임베딩 제공업체를 변경하려면 모든 데이터를 재인덱싱해야 합니다.",
|
|
118
|
+
"search.settings.change.title": "제공업체 변경 확인",
|
|
119
|
+
"search.settings.clearIndexDescription": "검색 엔진에서 모든 인덱싱된 데이터를 제거합니다.",
|
|
120
|
+
"search.settings.clearIndexDialogDescription": "인덱싱된 모든 데이터가 영구적으로 제거됩니다.",
|
|
121
|
+
"search.settings.clearIndexDialogTitle": "검색 인덱스 지우기",
|
|
122
|
+
"search.settings.clearIndexDialogWarning": "이 작업은 되돌릴 수 없습니다.",
|
|
123
|
+
"search.settings.clearIndexLabel": "인덱스 지우기",
|
|
124
|
+
"search.settings.dimension.label": "차원",
|
|
125
|
+
"search.settings.dimension.mismatch": "불일치",
|
|
126
|
+
"search.settings.documentsLabel": "문서",
|
|
127
|
+
"search.settings.errorLabel": "오류",
|
|
128
|
+
"search.settings.errors.dimensionRequired": "유효한 차원을 입력하세요",
|
|
129
|
+
"search.settings.errors.loadFailed": "설정을 불러오지 못했습니다",
|
|
130
|
+
"search.settings.errors.modelRequired": "모델 이름을 입력하세요",
|
|
131
|
+
"search.settings.errors.reindexFailed": "재인덱싱에 실패했습니다",
|
|
132
|
+
"search.settings.errors.saveFailed": "설정 저장에 실패했습니다",
|
|
133
|
+
"search.settings.fullReindexDescription": "전체 검색 인덱스를 처음부터 다시 빌드합니다.",
|
|
134
|
+
"search.settings.fullReindexDialogDescription": "모든 검색 인덱스를 다시 빌드합니다.",
|
|
135
|
+
"search.settings.fullReindexDialogTitle": "전체 재인덱싱",
|
|
136
|
+
"search.settings.fullReindexDialogWarning": "대용량 데이터셋의 경우 시간이 걸릴 수 있습니다.",
|
|
137
|
+
"search.settings.fullReindexLabel": "전체 재인덱싱",
|
|
138
|
+
"search.settings.fulltext.currentValue": "현재 값",
|
|
139
|
+
"search.settings.fulltext.defaultValue": "기본값",
|
|
140
|
+
"search.settings.fulltext.driver": "드라이버",
|
|
141
|
+
"search.settings.fulltext.envMissing": "환경 변수가 설정되지 않았습니다.",
|
|
142
|
+
"search.settings.fulltext.envSet": "환경 변수가 설정되어 있습니다.",
|
|
143
|
+
"search.settings.fulltext.envVars": "환경 변수",
|
|
144
|
+
"search.settings.fulltext.howTo": "구성 방법",
|
|
145
|
+
"search.settings.fulltext.howToDescription": "전체 텍스트 검색을 활성화하려면 필요한 환경 변수를 설정하세요.",
|
|
146
|
+
"search.settings.fulltext.learnMore": "더 알아보기",
|
|
147
|
+
"search.settings.fulltext.noDriver": "구성된 전체 텍스트 드라이버가 없습니다.",
|
|
148
|
+
"search.settings.fulltext.notReady": "준비되지 않음",
|
|
149
|
+
"search.settings.fulltext.optional": "선택 사항",
|
|
150
|
+
"search.settings.fulltext.ready": "준비됨",
|
|
151
|
+
"search.settings.fulltext.sectionDescription": "Meilisearch로 구동되는 전체 텍스트 검색을 구성합니다.",
|
|
152
|
+
"search.settings.fulltext.sectionTitle": "전체 텍스트 검색",
|
|
153
|
+
"search.settings.fulltextNotConfigured": "전체 텍스트 검색이 구성되지 않았습니다.",
|
|
154
|
+
"search.settings.fulltextNotConfiguredHint": "전체 텍스트 검색을 활성화하려면 MEILISEARCH_HOST를 설정하세요.",
|
|
155
|
+
"search.settings.globalSearch.description": "전역 검색 대화상자(Cmd+K)를 구동할 전략을 구성합니다.",
|
|
156
|
+
"search.settings.globalSearch.fulltext": "전체 텍스트",
|
|
157
|
+
"search.settings.globalSearch.fulltextDescription": "Meilisearch로 구동되는 오타에 강한 검색입니다.",
|
|
158
|
+
"search.settings.globalSearch.notConfigured": "구성되지 않음",
|
|
159
|
+
"search.settings.globalSearch.saveError": "전역 검색 설정 저장에 실패했습니다.",
|
|
160
|
+
"search.settings.globalSearch.title": "전역 검색",
|
|
161
|
+
"search.settings.globalSearch.tokens": "토큰",
|
|
162
|
+
"search.settings.globalSearch.tokensDescription": "PostgreSQL을 사용한 키워드 기반 검색입니다.",
|
|
163
|
+
"search.settings.globalSearch.vector": "벡터",
|
|
164
|
+
"search.settings.globalSearch.vectorDescription": "임베딩을 사용한 AI 기반 의미 기반 검색입니다.",
|
|
165
|
+
"search.settings.loading": "불러오는 중...",
|
|
166
|
+
"search.settings.loadingLabel": "불러오는 중…",
|
|
167
|
+
"search.settings.messages.providerSaved": "임베딩 제공업체가 저장되었습니다",
|
|
168
|
+
"search.settings.messages.reindexStarted": "재인덱싱이 시작되었습니다",
|
|
169
|
+
"search.settings.messages.saved": "설정이 저장되었습니다",
|
|
170
|
+
"search.settings.model.custom": "커스텀...",
|
|
171
|
+
"search.settings.model.label": "모델",
|
|
172
|
+
"search.settings.model.namePlaceholder": "모델 이름",
|
|
173
|
+
"search.settings.noIndexMessage": "아직 생성된 검색 인덱스가 없습니다.",
|
|
174
|
+
"search.settings.pageDescription": "검색 전략을 구성하고, 인덱스를 관리하고, 활동을 모니터링합니다.",
|
|
175
|
+
"search.settings.pageTitle": "검색 설정",
|
|
176
|
+
"search.settings.processingLabel": "처리 중…",
|
|
177
|
+
"search.settings.provider.label": "제공업체",
|
|
178
|
+
"search.settings.provider.notConfigured": "구성되지 않음",
|
|
179
|
+
"search.settings.provider.title": "임베딩 제공업체",
|
|
180
|
+
"search.settings.recreateIndexDescription": "검색 인덱스 구조를 삭제하고 다시 생성합니다.",
|
|
181
|
+
"search.settings.recreateIndexDialogDescription": "검색 인덱스를 삭제하고 다시 생성합니다.",
|
|
182
|
+
"search.settings.recreateIndexDialogTitle": "검색 인덱스 재생성",
|
|
183
|
+
"search.settings.recreateIndexDialogWarning": "인덱싱된 모든 데이터가 손실됩니다.",
|
|
184
|
+
"search.settings.recreateIndexLabel": "인덱스 재생성",
|
|
185
|
+
"search.settings.refreshLabel": "새로고침",
|
|
186
|
+
"search.settings.reindex.button": "재인덱싱 시작",
|
|
187
|
+
"search.settings.reindex.confirmButton": "재인덱싱 시작",
|
|
188
|
+
"search.settings.reindex.confirmDescription": "모든 벡터 임베딩을 다시 빌드합니다. 기존 데이터가 먼저 삭제됩니다.",
|
|
189
|
+
"search.settings.reindex.confirmTitle": "재인덱싱 확인",
|
|
190
|
+
"search.settings.reindex.description": "인덱싱된 모든 엔터티에 대해 벡터 임베딩을 다시 빌드합니다.",
|
|
191
|
+
"search.settings.reindex.running": "재인덱싱 중...",
|
|
192
|
+
"search.settings.reindex.title": "데이터 재인덱싱",
|
|
193
|
+
"search.settings.reindex.warning": "대용량 데이터셋의 경우 시간이 걸릴 수 있습니다.",
|
|
194
|
+
"search.settings.reindexErrorLabel": "재인덱싱 실패",
|
|
195
|
+
"search.settings.reindexInProgress": "재인덱싱 진행 중",
|
|
196
|
+
"search.settings.reindexInProgressDetails": "현재 재인덱싱 작업이 실행 중입니다. 완료될 때까지 기다려 주세요.",
|
|
197
|
+
"search.settings.reindexSuccessLabel": "재인덱싱 완료",
|
|
198
|
+
"search.settings.status.configured": "벡터 검색이 구성됨",
|
|
199
|
+
"search.settings.status.notConfigured": "벡터 검색이 구성되지 않음",
|
|
200
|
+
"search.settings.status.title": "자동 인덱싱",
|
|
201
|
+
"search.settings.tabs.activity": "활동",
|
|
202
|
+
"search.settings.tabs.configuration": "구성",
|
|
203
|
+
"search.settings.tabs.indexManagement": "인덱스 관리",
|
|
204
|
+
"search.settings.title": "검색 설정",
|
|
205
|
+
"search.settings.vector.active": "활성",
|
|
206
|
+
"search.settings.vector.comingSoon": "출시 예정",
|
|
207
|
+
"search.settings.vector.howTo": "구성 방법",
|
|
208
|
+
"search.settings.vector.howToDescription": "벡터 검색을 활성화하려면 임베딩 제공업체를 설정하세요.",
|
|
209
|
+
"search.settings.vector.inheritingDefault": "이 테넌트는 인스턴스/환경 기본값을 상속받고 있습니다. 제공자를 저장하면 이 테넌트에 대한 오버라이드가 생성됩니다.",
|
|
210
|
+
"search.settings.vector.modelsAvailable": "사용 가능한 모델",
|
|
211
|
+
"search.settings.vector.providers": "제공업체",
|
|
212
|
+
"search.settings.vector.providersHint": "환경 변수를 통해 임베딩 제공업체를 구성하세요.",
|
|
213
|
+
"search.settings.vector.sectionDescription": "벡터 임베딩을 사용한 AI 기반 의미 기반 검색을 구성합니다.",
|
|
214
|
+
"search.settings.vector.sectionTitle": "벡터 검색",
|
|
215
|
+
"search.settings.vector.setEnvVar": "환경 변수 설정",
|
|
216
|
+
"search.settings.vector.store": "벡터 저장소",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "벡터 저장소를 사용할 수 없습니다",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "저장소에 연결할 수 있을 때까지 벡터 인덱싱과 의미 기반 검색은 건너뜁니다. PostgreSQL 서버에 pgvector 확장을 설치하세요. 상태는 약 1분마다 다시 확인됩니다.",
|
|
219
|
+
"search.settings.vector.usingTenantConfig": "이 테넌트는 자체 임베딩 설정을 사용하고 있습니다.",
|
|
220
|
+
"search.settings.vectorDocumentsLabel": "벡터 문서",
|
|
221
|
+
"search.settings.vectorNotConfigured": "벡터 검색이 구성되지 않았습니다.",
|
|
222
|
+
"search.settings.vectorNotConfiguredHint": "벡터 검색을 활성화하려면 임베딩 제공업체 API 키를 설정하세요.",
|
|
223
|
+
"search.settings.vectorReindex.button": "벡터 재인덱싱 시작",
|
|
224
|
+
"search.settings.vectorReindex.description": "원본 데이터로부터 모든 벡터 임베딩을 다시 빌드합니다.",
|
|
225
|
+
"search.settings.vectorReindex.running": "벡터 재인덱싱 진행 중…",
|
|
226
|
+
"search.settings.vectorReindex.title": "벡터 재인덱싱",
|
|
227
|
+
"search.settings.vectorReindex.warning": "기존 임베딩을 삭제하고 다시 생성합니다.",
|
|
228
|
+
"search.table.actions.open": "열기",
|
|
229
|
+
"search.table.columns.result": "결과",
|
|
230
|
+
"search.table.columns.score": "점수",
|
|
231
|
+
"search.table.columns.source": "소스",
|
|
232
|
+
"search.table.errors.noSources": "사용 가능한 검색 소스가 없습니다.",
|
|
233
|
+
"search.table.errors.searchFailed": "검색에 실패했습니다.",
|
|
234
|
+
"search.table.searchPlaceholder": "검색…",
|
|
235
|
+
"search.table.sources": "소스",
|
|
236
|
+
"search.table.stats.results": "결과",
|
|
237
|
+
"search.table.stats.sources": "소스",
|
|
238
|
+
"search.table.title": "검색 결과"
|
|
239
|
+
}
|
|
@@ -50,6 +50,53 @@
|
|
|
50
50
|
"search.dialog.strategies.toggle": "Opcje wyszukiwania",
|
|
51
51
|
"search.dialog.title": "Globalne Wyszukiwanie",
|
|
52
52
|
"search.dialog.warnings.configureLink": "Skonfiguruj wyszukiwanie",
|
|
53
|
+
"search.entityType.catalog.catalog_offer": "Oferta",
|
|
54
|
+
"search.entityType.catalog.catalog_option_schema_template": "Szablon opcji",
|
|
55
|
+
"search.entityType.catalog.catalog_price_kind": "Typ ceny",
|
|
56
|
+
"search.entityType.catalog.catalog_product": "Produkt",
|
|
57
|
+
"search.entityType.catalog.catalog_product_category": "Kategoria",
|
|
58
|
+
"search.entityType.catalog.catalog_product_tag": "Tag",
|
|
59
|
+
"search.entityType.catalog.catalog_product_unit_conversion": "Przelicznik jednostek",
|
|
60
|
+
"search.entityType.catalog.catalog_product_variant": "Wariant",
|
|
61
|
+
"search.entityType.customer_accounts.customer_role": "Rola klienta",
|
|
62
|
+
"search.entityType.customer_accounts.customer_user": "Użytkownik klienta",
|
|
63
|
+
"search.entityType.customers.customer_activity": "Aktywność",
|
|
64
|
+
"search.entityType.customers.customer_comment": "Komentarz",
|
|
65
|
+
"search.entityType.customers.customer_company_profile": "Firma",
|
|
66
|
+
"search.entityType.customers.customer_deal": "Szansa sprzedaży",
|
|
67
|
+
"search.entityType.customers.customer_entity": "Klient",
|
|
68
|
+
"search.entityType.customers.customer_person_profile": "Osoba",
|
|
69
|
+
"search.entityType.customers.customer_todo_link": "Zadanie",
|
|
70
|
+
"search.entityType.inbox_ops.inbox_proposal": "Propozycja skrzynki",
|
|
71
|
+
"search.entityType.messages.message": "Wiadomość",
|
|
72
|
+
"search.entityType.planner.planner_availability_rule_set": "Reguły dostępności",
|
|
73
|
+
"search.entityType.resources.resources_resource": "Zasób",
|
|
74
|
+
"search.entityType.resources.resources_resource_type": "Typ zasobu",
|
|
75
|
+
"search.entityType.sales.sales_channel": "Kanał",
|
|
76
|
+
"search.entityType.sales.sales_credit_memo": "Nota kredytowa",
|
|
77
|
+
"search.entityType.sales.sales_credit_memo_line": "Pozycja noty kredytowej",
|
|
78
|
+
"search.entityType.sales.sales_delivery_window": "Okno dostawy",
|
|
79
|
+
"search.entityType.sales.sales_document_address": "Adres dokumentu",
|
|
80
|
+
"search.entityType.sales.sales_document_tag": "Tag dokumentu",
|
|
81
|
+
"search.entityType.sales.sales_invoice": "Faktura",
|
|
82
|
+
"search.entityType.sales.sales_invoice_line": "Pozycja faktury",
|
|
83
|
+
"search.entityType.sales.sales_note": "Notatka",
|
|
84
|
+
"search.entityType.sales.sales_order": "Zamówienie",
|
|
85
|
+
"search.entityType.sales.sales_order_adjustment": "Korekta zamówienia",
|
|
86
|
+
"search.entityType.sales.sales_order_line": "Pozycja zamówienia",
|
|
87
|
+
"search.entityType.sales.sales_payment": "Płatność",
|
|
88
|
+
"search.entityType.sales.sales_payment_allocation": "Przypisanie płatności",
|
|
89
|
+
"search.entityType.sales.sales_payment_method": "Metoda płatności",
|
|
90
|
+
"search.entityType.sales.sales_quote": "Oferta",
|
|
91
|
+
"search.entityType.sales.sales_quote_adjustment": "Korekta oferty",
|
|
92
|
+
"search.entityType.sales.sales_quote_line": "Pozycja oferty",
|
|
93
|
+
"search.entityType.sales.sales_shipment": "Wysyłka",
|
|
94
|
+
"search.entityType.sales.sales_shipment_item": "Pozycja wysyłki",
|
|
95
|
+
"search.entityType.sales.sales_shipping_method": "Metoda wysyłki",
|
|
96
|
+
"search.entityType.sales.sales_tax_rate": "Stawka podatku",
|
|
97
|
+
"search.entityType.staff.staff_team": "Zespół",
|
|
98
|
+
"search.entityType.staff.staff_team_member": "Członek zespołu",
|
|
99
|
+
"search.entityType.staff.staff_team_role": "Rola w zespole",
|
|
53
100
|
"search.messages.missingConfig": "Wyszukiwanie wymaga skonfigurowania dostawcy embeddingów dla wyszukiwania semantycznego.",
|
|
54
101
|
"search.nav.hybridSearch": "Wyszukiwanie",
|
|
55
102
|
"search.scopeHint.currentOrg": "Zakres: bieżąca organizacja",
|
|
@@ -167,6 +214,8 @@
|
|
|
167
214
|
"search.settings.vector.sectionTitle": "Wyszukiwanie wektorowe",
|
|
168
215
|
"search.settings.vector.setEnvVar": "Ustaw zmienną środowiskową",
|
|
169
216
|
"search.settings.vector.store": "Magazyn wektorów",
|
|
217
|
+
"search.settings.vector.storeUnavailable": "Magazyn wektorów jest niedostępny",
|
|
218
|
+
"search.settings.vector.storeUnavailableHint": "Indeksowanie wektorowe i wyszukiwanie semantyczne są pomijane, dopóki magazyn nie będzie dostępny. Zainstaluj rozszerzenie pgvector na serwerze PostgreSQL; status jest sprawdzany ponownie mniej więcej co minutę.",
|
|
170
219
|
"search.settings.vector.usingTenantConfig": "Ten najemca używa własnych ustawień osadzania.",
|
|
171
220
|
"search.settings.vectorDocumentsLabel": "Dokumenty wektorowe",
|
|
172
221
|
"search.settings.vectorNotConfigured": "Wyszukiwanie wektorowe nie jest skonfigurowane.",
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import type { ConfigScope, ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
envDisablesAutoIndexing,
|
|
4
|
+
SEARCH_AUTO_INDEX_CONFIG_KEY,
|
|
5
|
+
SEARCH_AUTO_INDEX_CONFIG_MODULE,
|
|
6
|
+
} from '@open-mercato/shared/lib/search/auto-indexing'
|
|
3
7
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
export function envDisablesAutoIndexing(): boolean {
|
|
7
|
-
const raw =
|
|
8
|
-
process.env.OM_DISABLE_VECTOR_SEARCH_AUTOINDEXING ??
|
|
9
|
-
process.env.DISABLE_VECTOR_SEARCH_AUTOINDEXING
|
|
10
|
-
if (!raw) return false
|
|
11
|
-
return parseBooleanToken(raw) === true
|
|
12
|
-
}
|
|
8
|
+
export { envDisablesAutoIndexing, SEARCH_AUTO_INDEX_CONFIG_KEY, SEARCH_AUTO_INDEX_CONFIG_MODULE }
|
|
13
9
|
|
|
14
10
|
type Resolver = {
|
|
15
11
|
resolve: <T = unknown>(name: string) => T
|
|
@@ -28,8 +24,7 @@ export async function resolveAutoIndexingEnabled(
|
|
|
28
24
|
return fallback
|
|
29
25
|
}
|
|
30
26
|
try {
|
|
31
|
-
|
|
32
|
-
const value = await service.getValue<boolean>('vector', SEARCH_AUTO_INDEX_CONFIG_KEY, {
|
|
27
|
+
const value = await service.getValue<boolean>(SEARCH_AUTO_INDEX_CONFIG_MODULE, SEARCH_AUTO_INDEX_CONFIG_KEY, {
|
|
33
28
|
defaultValue: fallback,
|
|
34
29
|
scope: options?.scope,
|
|
35
30
|
})
|
package/src/service.ts
CHANGED
|
@@ -10,7 +10,6 @@ import type {
|
|
|
10
10
|
} from './types'
|
|
11
11
|
import { mergeAndRankResults } from './lib/merger'
|
|
12
12
|
import { searchError } from './lib/debug'
|
|
13
|
-
import { needsSearchResultEnrichment } from './lib/search-result-enrichment'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Default merge configuration.
|
|
@@ -163,8 +162,8 @@ export class SearchService {
|
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
165
|
+
* Recompute configured presenters at request time and fill missing presenter
|
|
166
|
+
* or navigation data for unconfigured results.
|
|
168
167
|
*/
|
|
169
168
|
private async enrichResultsWithPresenter(
|
|
170
169
|
results: SearchResult[],
|
|
@@ -174,10 +173,6 @@ export class SearchService {
|
|
|
174
173
|
// If no enricher configured, return as-is
|
|
175
174
|
if (!this.presenterEnricher) return results
|
|
176
175
|
|
|
177
|
-
const hasMissing = results.some(needsSearchResultEnrichment)
|
|
178
|
-
if (!hasMissing) return results
|
|
179
|
-
|
|
180
|
-
// Use the configured presenter enricher
|
|
181
176
|
try {
|
|
182
177
|
return await this.presenterEnricher(results, tenantId, organizationId)
|
|
183
178
|
} catch {
|
|
@@ -59,7 +59,13 @@ export class VectorSearchStrategy implements SearchStrategy {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async isAvailable(): Promise<boolean> {
|
|
62
|
-
|
|
62
|
+
if (!this.embeddingService.available) return false
|
|
63
|
+
// A configured embedding provider says nothing about the vector store. When the
|
|
64
|
+
// store cannot serve writes (pgvector extension missing), reporting availability
|
|
65
|
+
// here makes SearchService call ensureReady() for every record and fail the whole
|
|
66
|
+
// index/delete operation. Drivers without a probe keep the previous behavior.
|
|
67
|
+
if (!this.vectorDriver.isHealthy) return true
|
|
68
|
+
return this.vectorDriver.isHealthy()
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
async ensureReady(): Promise<void> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pool } from 'pg'
|
|
2
|
-
import { searchDebugWarn } from '../../../lib/debug'
|
|
2
|
+
import { searchDebugWarn, searchWarn } from '../../../lib/debug'
|
|
3
3
|
|
|
4
4
|
type PgPoolQueryResult<T> = { rows: T[]; rowCount?: number }
|
|
5
5
|
type PgPoolClient = {
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
VectorDriverRemoveOrphansParams,
|
|
23
23
|
VectorResultPresenter,
|
|
24
24
|
VectorLinkDescriptor,
|
|
25
|
+
VectorDriverStatus,
|
|
25
26
|
} from '../../types'
|
|
26
27
|
|
|
27
28
|
type PgVectorDriverOptions = {
|
|
@@ -38,6 +39,16 @@ const DEFAULT_MIGRATIONS_TABLE = 'vector_search_migrations'
|
|
|
38
39
|
const DEFAULT_DIMENSION = 1536
|
|
39
40
|
const DRIVER_ID = 'pgvector' as const
|
|
40
41
|
|
|
42
|
+
// `undefined_file` is what Postgres raises for `extension "vector" is not available`
|
|
43
|
+
// (the pgvector shared library is not installed on the server); `feature_not_supported`
|
|
44
|
+
// covers managed providers that refuse the extension outright, and `undefined_object`
|
|
45
|
+
// the case where the extension record is missing. None of these can be resolved by
|
|
46
|
+
// retrying, so the driver reports itself unhealthy instead of failing every record.
|
|
47
|
+
const EXTENSION_MISSING_CODES = new Set(['58P01', '0A000', '42704'])
|
|
48
|
+
const EXTENSION_UNAVAILABLE_REASON = 'extension "vector" is not available on this PostgreSQL server'
|
|
49
|
+
// Re-probe occasionally so installing pgvector recovers without restarting the process.
|
|
50
|
+
const UNAVAILABLE_RECHECK_MS = 60_000
|
|
51
|
+
|
|
41
52
|
function assertIdentifier(name: string, defaultName: string): string {
|
|
42
53
|
const candidate = name ?? defaultName
|
|
43
54
|
if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName
|
|
@@ -102,8 +113,60 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
102
113
|
})()
|
|
103
114
|
|
|
104
115
|
let ready: Promise<void> | null = null
|
|
116
|
+
let status: { value: VectorDriverStatus; at: number } | null = null
|
|
117
|
+
let reportedReason: string | null = null
|
|
118
|
+
|
|
119
|
+
const cacheStatus = (value: VectorDriverStatus): VectorDriverStatus => {
|
|
120
|
+
status = { value, at: Date.now() }
|
|
121
|
+
if (value.available) {
|
|
122
|
+
reportedReason = null
|
|
123
|
+
} else if (value.reason && value.reason !== reportedReason) {
|
|
124
|
+
reportedReason = value.reason
|
|
125
|
+
// Always visible: replacing a per-record error storm with silence would hide the fact
|
|
126
|
+
// that vector indexing has stopped until someone opens Settings > Search.
|
|
127
|
+
searchWarn('vector.pgvector', `vector store unavailable — skipping vector indexing and search (${value.reason})`)
|
|
128
|
+
}
|
|
129
|
+
return value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The operator-facing reason is curated rather than the raw Postgres message, which the
|
|
133
|
+
// settings API surfaces to the browser.
|
|
134
|
+
const markExtensionMissing = () => {
|
|
135
|
+
cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const readCachedStatus = (): VectorDriverStatus | null => {
|
|
139
|
+
if (!status) return null
|
|
140
|
+
return Date.now() - status.at < UNAVAILABLE_RECHECK_MS ? status.value : null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getStatus = async (): Promise<VectorDriverStatus> => {
|
|
144
|
+
const cached = readCachedStatus()
|
|
145
|
+
if (cached) return cached
|
|
146
|
+
try {
|
|
147
|
+
const res = await pool.query<{ installed: boolean; installable: boolean }>(
|
|
148
|
+
`SELECT
|
|
149
|
+
EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') AS installed,
|
|
150
|
+
EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') AS installable`,
|
|
151
|
+
)
|
|
152
|
+
const row = res.rows[0]
|
|
153
|
+
if (row?.installed || row?.installable) return cacheStatus({ available: true })
|
|
154
|
+
return cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })
|
|
155
|
+
} catch {
|
|
156
|
+
// A probe that cannot run (connection refused, permissions on the catalogs)
|
|
157
|
+
// says nothing about pgvector itself — stay optimistic so genuinely transient
|
|
158
|
+
// database problems keep their existing retry semantics.
|
|
159
|
+
return { available: true }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const isHealthy = async (): Promise<boolean> => (await getStatus()).available
|
|
105
164
|
|
|
106
165
|
const ensureReady = async () => {
|
|
166
|
+
const cached = readCachedStatus()
|
|
167
|
+
if (cached && !cached.available) {
|
|
168
|
+
throw new Error(`[vector.pgvector] ${cached.reason ?? 'vector store unavailable'}`)
|
|
169
|
+
}
|
|
107
170
|
if (!ready) {
|
|
108
171
|
ready = withClient(pool, async (client) => {
|
|
109
172
|
const ensureExtension = async (extension: 'pgcrypto' | 'vector') => {
|
|
@@ -116,6 +179,9 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
116
179
|
searchDebugWarn('vector.pgvector', `skipping ${extension} extension creation; requires superuser${details}`)
|
|
117
180
|
return
|
|
118
181
|
}
|
|
182
|
+
if (extension === 'vector' && pgError?.code && EXTENSION_MISSING_CODES.has(pgError.code)) {
|
|
183
|
+
markExtensionMissing()
|
|
184
|
+
}
|
|
119
185
|
throw error
|
|
120
186
|
}
|
|
121
187
|
}
|
|
@@ -219,8 +285,16 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
219
285
|
`INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,
|
|
220
286
|
['0001_init'],
|
|
221
287
|
)
|
|
288
|
+
}).then(() => {
|
|
289
|
+
cacheStatus({ available: true })
|
|
222
290
|
}).catch((err) => {
|
|
223
291
|
ready = null
|
|
292
|
+
const pgError = err as { code?: string; message?: string }
|
|
293
|
+
// Without the extension the `vector` column type does not exist either, so the
|
|
294
|
+
// table DDL fails even when `CREATE EXTENSION` was skipped rather than raised.
|
|
295
|
+
if (pgError?.code === '42704' && /\bvector\b/.test(pgError.message ?? '')) {
|
|
296
|
+
markExtensionMissing()
|
|
297
|
+
}
|
|
224
298
|
throw err
|
|
225
299
|
})
|
|
226
300
|
}
|
|
@@ -633,6 +707,8 @@ export function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDr
|
|
|
633
707
|
return {
|
|
634
708
|
id: 'pgvector',
|
|
635
709
|
ensureReady,
|
|
710
|
+
isHealthy,
|
|
711
|
+
getStatus,
|
|
636
712
|
upsert,
|
|
637
713
|
delete: remove,
|
|
638
714
|
getChecksum,
|
package/src/vector/types.ts
CHANGED
|
@@ -96,9 +96,23 @@ export type VectorDriverRemoveOrphansParams = {
|
|
|
96
96
|
olderThan: Date
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export type VectorDriverStatus = {
|
|
100
|
+
available: boolean
|
|
101
|
+
reason?: string
|
|
102
|
+
}
|
|
103
|
+
|
|
99
104
|
export interface VectorDriver {
|
|
100
105
|
readonly id: VectorDriverId
|
|
101
106
|
ensureReady(): Promise<void>
|
|
107
|
+
/**
|
|
108
|
+
* Cheap, cached probe answering whether the backing store can serve reads and
|
|
109
|
+
* writes at all. Drivers that cannot satisfy their hard requirements (for
|
|
110
|
+
* pgvector: the `vector` extension) MUST report `false` here so callers skip
|
|
111
|
+
* the strategy instead of failing every record. Optional for backward
|
|
112
|
+
* compatibility — an absent implementation is treated as available.
|
|
113
|
+
*/
|
|
114
|
+
isHealthy?(): Promise<boolean>
|
|
115
|
+
getStatus?(): Promise<VectorDriverStatus>
|
|
102
116
|
upsert(doc: VectorDriverDocument): Promise<void>
|
|
103
117
|
delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>
|
|
104
118
|
query(input: VectorDriverQuery): Promise<VectorDriverQueryResult[]>
|