Skip to content

Commit 2b7f24d

Browse files
authored
Merge pull request #83 from oramasearch/feature/orm-1734
Feature/orm 1734
2 parents cb8ce68 + f5c8653 commit 2b7f24d

File tree

30 files changed

+642
-394
lines changed

30 files changed

+642
-394
lines changed

apps/demo-react/src/App.tsx

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ function App() {
1212
<div>
1313
<nav>
1414
<NavLink to="/" end>
15-
ChatBox
15+
SearchBox
1616
</NavLink>
1717
<NavLink to="/chat" end>
18-
SearchBox
18+
ChatBox
1919
</NavLink>
2020
</nav>
2121
<Routes>
22-
<Route path="/" element={<ChatBoxPage />} />
23-
<Route path="/chat" element={<SearchBoxPage />} />
22+
<Route path="/" element={<SearchBoxPage />} />
23+
<Route path="/chat" element={<ChatBoxPage />} />
2424
</Routes>
2525
</div>
2626
</BrowserRouter>
@@ -31,40 +31,54 @@ const ChatBoxPage = () => {
3131
return (
3232
<>
3333
<main>
34-
<section>
35-
<h1>App React</h1>
36-
<p>
37-
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem
38-
aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
39-
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni
40-
dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor
41-
sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore
42-
magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis
43-
suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in
44-
ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas
45-
nulla pariatur?
46-
</p>
47-
<section>
48-
<h2>ChatBox in a section</h2>
49-
<div className="component-row">
50-
<OramaChatBox
51-
index={{
52-
api_key: API_KEY,
53-
endpoint: ENDPOINT,
54-
}}
55-
style={{ height: '600px' }}
56-
onAnswerSourceClick={(e: Event) => console.log(e)}
57-
onAnswerGenerated={(e: Event) => console.log(e)}
58-
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
59-
chatMarkdownLinkHref={({ href }) => href}
60-
onChatMarkdownLinkClicked={(e: Event) => {
61-
console.log(e)
62-
e.preventDefault()
63-
}}
64-
/>
65-
</div>
66-
</section>
67-
</section>
34+
<h2 style={{ textAlign: 'center' }}>CHAT BOX</h2>
35+
<div className="component-row">
36+
<OramaChatBox
37+
index={{
38+
api_key: API_KEY,
39+
endpoint: ENDPOINT,
40+
}}
41+
style={{ height: '600px' }}
42+
onAnswerSourceClick={(e: Event) => console.log(e)}
43+
onAnswerGenerated={(e: Event) => console.log(e)}
44+
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
45+
chatMarkdownLinkHref={({ href }) => href}
46+
onChatMarkdownLinkClicked={(e: Event) => {
47+
console.log(e)
48+
e.preventDefault()
49+
}}
50+
/>
51+
<OramaChatBox
52+
index={{
53+
api_key: API_KEY,
54+
endpoint: ENDPOINT,
55+
}}
56+
style={{ height: '600px' }}
57+
onAnswerSourceClick={(e: Event) => console.log(e)}
58+
onAnswerGenerated={(e: Event) => console.log(e)}
59+
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
60+
chatMarkdownLinkHref={({ href }) => href}
61+
onChatMarkdownLinkClicked={(e: Event) => {
62+
console.log(e)
63+
e.preventDefault()
64+
}}
65+
/>
66+
<OramaChatBox
67+
index={{
68+
api_key: API_KEY,
69+
endpoint: ENDPOINT,
70+
}}
71+
style={{ height: '600px' }}
72+
onAnswerSourceClick={(e: Event) => console.log(e)}
73+
onAnswerGenerated={(e: Event) => console.log(e)}
74+
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
75+
chatMarkdownLinkHref={({ href }) => href}
76+
onChatMarkdownLinkClicked={(e: Event) => {
77+
console.log(e)
78+
e.preventDefault()
79+
}}
80+
/>
81+
</div>
6882
</main>
6983
</>
7084
)
@@ -75,36 +89,35 @@ const SearchBoxPage = () => {
7589
return (
7690
<>
7791
<main>
78-
<section>
79-
<div className="component-row">
80-
<OramaSearchButton colorScheme="system">Search</OramaSearchButton>
81-
<OramaSearchBox
82-
colorScheme="system"
83-
index={{
84-
api_key: API_KEY,
85-
endpoint: ENDPOINT,
86-
}}
87-
suggestions={['Suggestion 1', 'Suggestion 2', 'Suggestion 3']}
88-
onSearchCompleted={(e: Event) => console.log(e)}
89-
onSearchResultClick={(e) => {
90-
e.preventDefault()
91-
alert('Moving back to home page')
92-
navigate('/')
93-
}}
94-
onAnswerGenerated={(e: Event) => console.log(e)}
95-
onAnswerSourceClick={(e: Event) => {
96-
console.log(e)
97-
e.preventDefault()
98-
}}
99-
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
100-
chatMarkdownLinkHref={({ href }) => href}
101-
onChatMarkdownLinkClicked={(e) => {
102-
alert('Callback on client side')
103-
e.preventDefault()
104-
}}
105-
/>
106-
</div>
107-
</section>
92+
<h2 style={{ textAlign: 'center' }}>SEARCH BOX</h2>
93+
<div className="component-row">
94+
<OramaSearchButton colorScheme="system">Search</OramaSearchButton>
95+
<OramaSearchBox
96+
colorScheme="system"
97+
index={{
98+
api_key: API_KEY,
99+
endpoint: ENDPOINT,
100+
}}
101+
suggestions={['Suggestion 1', 'Suggestion 2', 'Suggestion 3']}
102+
onSearchCompleted={(e: Event) => console.log(e)}
103+
onSearchResultClick={(e) => {
104+
e.preventDefault()
105+
alert('Moving back to home page')
106+
navigate('/')
107+
}}
108+
onAnswerGenerated={(e: Event) => console.log(e)}
109+
onAnswerSourceClick={(e: Event) => {
110+
console.log(e)
111+
e.preventDefault()
112+
}}
113+
chatMarkdownLinkTitle={({ text }) => text?.toUpperCase()}
114+
chatMarkdownLinkHref={({ href }) => href}
115+
onChatMarkdownLinkClicked={(e) => {
116+
alert('Callback on client side')
117+
e.preventDefault()
118+
}}
119+
/>
120+
</div>
108121
</main>
109122
</>
110123
)

apps/demo-react/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,8 @@ main {
126126
width: 100%;
127127
max-width: 800px;
128128
margin: 0 auto;
129+
padding: 32px 32px;
130+
display: flex;
131+
flex-direction: column;
132+
gap: 32px;
129133
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { ChatService } from '@/services/ChatService'
2+
import type { SourcesMap, TChatInteraction } from '@/types'
3+
import type { ObservableMap } from '@stencil/store'
4+
5+
export const ChatStoreInitialProps = {
6+
chatService: null as ChatService | null,
7+
interactions: [] as TChatInteraction[] | null,
8+
sourceBaseURL: '' as string,
9+
linksTarget: '_blank' as string,
10+
linksRel: 'noopener noreferrer' as string,
11+
prompt: '',
12+
sourcesMap: {
13+
title: 'title',
14+
description: 'description',
15+
path: 'path',
16+
} as SourcesMap,
17+
}
18+
19+
export type ChatStoreType = ObservableMap<typeof ChatStoreInitialProps>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { ObservableMap } from '@stencil/store'
2+
3+
export const GlobalStoreInitialProps = {
4+
open: false,
5+
currentTask: 'search' as 'search' | 'chat',
6+
currentTerm: '',
7+
}
8+
export type GlobalStoreType = ObservableMap<typeof GlobalStoreInitialProps>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { ChatStoreInitialProps, type ChatStoreType } from '@/ParentComponentStore/ChatStore'
2+
import { type GlobalStoreType, GlobalStoreInitialProps } from '@/ParentComponentStore/GlobalStore'
3+
import { SearchStoreInitialProps, type SearchStoreType } from '@/ParentComponentStore/SearchStore'
4+
import { getExternalComponentHTMLElement } from '@/utils/utils'
5+
import { createStore } from '@stencil/store'
6+
7+
export type StoresMapType = {
8+
global?: GlobalStoreType
9+
search?: SearchStoreType
10+
chat?: ChatStoreType
11+
}
12+
13+
// biome-ignore lint/suspicious/noExplicitAny: Any is acceptable here
14+
const storesMapInitialProps: Record<keyof StoresMapType, any> = {
15+
global: GlobalStoreInitialProps,
16+
search: SearchStoreInitialProps,
17+
chat: ChatStoreInitialProps,
18+
}
19+
20+
export type StoresMapKeys = keyof StoresMapType
21+
22+
/**
23+
* Holds the global references for all stores in Orama Ui.
24+
*
25+
*/
26+
const storesReferencesMap = new Map<
27+
string,
28+
{ search?: SearchStoreType; chat?: ChatStoreType; global?: GlobalStoreType }
29+
>()
30+
31+
/**
32+
* Access storeSReferenceMap and return the initialized store. Throw exception if either parent or store does not exist
33+
*
34+
* @param parentComponentId
35+
* @param storeName
36+
* @returns
37+
*/
38+
const getParentComponentStore = <T extends StoresMapKeys>(
39+
parentComponentId: string,
40+
storeName: T,
41+
): StoresMapType[T] => {
42+
const parentStores = storesReferencesMap.get(parentComponentId)
43+
44+
if (!parentStores) {
45+
throw new Error('Invalid parent component Id')
46+
}
47+
48+
const store = parentStores[storeName]
49+
50+
if (!store) {
51+
throw new Error('Store not initialized')
52+
}
53+
54+
return store
55+
}
56+
57+
export const initStore = <T extends StoresMapKeys>(storeName: T, parentComponentId: string): StoresMapType[T] => {
58+
const initialProps = storesMapInitialProps[storeName]
59+
if (!initialProps) {
60+
throw new Error('Invalid store name')
61+
}
62+
63+
const store = createStore(initialProps)
64+
65+
if (storesReferencesMap.has(parentComponentId)) {
66+
const currentStores = storesReferencesMap.get(parentComponentId)
67+
currentStores[storeName] = store
68+
} else {
69+
storesReferencesMap.set(parentComponentId, { [storeName]: store })
70+
}
71+
72+
return store
73+
}
74+
75+
export const removeAllStores = (parentComponentId: string) => {
76+
storesReferencesMap.delete(parentComponentId)
77+
}
78+
79+
export function getStore<T extends StoresMapKeys>(storeName: T, element: HTMLElement) {
80+
const externalComponent = getExternalComponentHTMLElement(element)
81+
82+
if (!externalComponent) {
83+
throw new Error('Failed to get store')
84+
}
85+
86+
return getParentComponentStore(externalComponent.id, storeName)
87+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import type { Facet } from '@/types'
21
import type { SearchService } from '@/services/SearchService'
3-
import { createStore } from '@stencil/store'
4-
import type { ResultMap, SearchResultBySection } from '@/types'
2+
import type { Facet, ResultMap, SearchResultBySection } from '@/types'
53
import type { AnyOrama, Orama, SearchParams } from '@orama/orama'
64
import type { OramaClient } from '@oramacloud/client'
5+
import type { ObservableMap } from '@stencil/store'
76

8-
const store = createStore({
7+
export const SearchStoreInitialProps = {
98
count: 0,
109
facets: [] as Facet[],
1110
facetProperty: '', // TODO: consider to move to resultsMap
@@ -18,8 +17,6 @@ const store = createStore({
1817
// Lets queckly dicudd about this again.
1918
searchService: null as SearchService | null,
2019
searchParams: null as SearchParams<Orama<AnyOrama | OramaClient>>,
21-
})
20+
}
2221

23-
const { state: searchState, ...searchStore } = store
24-
25-
export { searchState, searchStore }
22+
export type SearchStoreType = ObservableMap<typeof SearchStoreInitialProps>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getStore, type StoresMapKeys } from '@/ParentComponentStore/ParentComponentStoreManager'
2+
import { type ComponentInterface, getElement } from '@stencil/core/internal'
3+
4+
// Define unique symbols for internal metadata
5+
const STORE_PROPS = Symbol('storeProps')
6+
const STORE_WILL_LOAD_PATCHED = Symbol('storeWillLoadPatched')
7+
8+
/**
9+
* A decorator that:
10+
* - Patches `componentWillLoad` to initialize a Component Store.
11+
*/
12+
export function Store<S extends StoresMapKeys>(storeName: S): PropertyDecorator {
13+
return (targetClass: ComponentInterface, propKey: string) => {
14+
const classConstructor = targetClass.constructor
15+
16+
// Initialize the store properties metadata array if needed.
17+
if (!classConstructor[STORE_PROPS]) {
18+
classConstructor[STORE_PROPS] = []
19+
}
20+
// Add the property key to our metadata.
21+
classConstructor[STORE_PROPS].push(propKey)
22+
23+
// Patch componentWillLoad only once for the class.
24+
if (!classConstructor[STORE_WILL_LOAD_PATCHED]) {
25+
classConstructor[STORE_WILL_LOAD_PATCHED] = true
26+
27+
const originalComponentWillLoad = targetClass.componentWillLoad
28+
29+
// Replace componentWillLoad with our own wrapped version.
30+
targetClass.componentWillLoad = function () {
31+
// Access the host element
32+
const hostEl = getElement(this)
33+
34+
const storeProps: (string | symbol)[] = this.constructor[STORE_PROPS]
35+
if (storeProps) {
36+
for (const storeProp of storeProps) {
37+
const store = getStore(storeName, hostEl)
38+
this[storeProp] = store
39+
}
40+
}
41+
42+
// Call the original componentWillLoad (if it exists).
43+
if (typeof originalComponentWillLoad === 'function') {
44+
originalComponentWillLoad.apply(this)
45+
}
46+
}
47+
}
48+
}
49+
}

packages/ui-stencil/src/components.d.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
*/
77
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
88
import { ButtonProps } from "./components/internal/orama-button/orama-button";
9-
import { ChatMarkdownLinkHref, ChatMarkdownLinkTarget, ChatMarkdownLinkTitle, CloudIndexConfig, ColorScheme, Facet, OnAnswerGeneratedCallbackProps, OnAnswerSourceClickCallbackProps, OnChatMarkdownLinkClickedCallbackProps, OnSearchCompletedCallbackProps, OnSearchResultClickCallbackProps, onStartConversationCallbackProps, ResultItemRenderFunction, ResultMap, SearchResultBySection, SourcesMap } from "./types/index";
10-
import { TChatInteraction } from "./context/chatContext";
9+
import { ChatMarkdownLinkHref, ChatMarkdownLinkTarget, ChatMarkdownLinkTitle, CloudIndexConfig, ColorScheme, Facet, OnAnswerGeneratedCallbackProps, OnAnswerSourceClickCallbackProps, OnChatMarkdownLinkClickedCallbackProps, OnSearchCompletedCallbackProps, OnSearchResultClickCallbackProps, onStartConversationCallbackProps, ResultItemRenderFunction, ResultMap, SearchResultBySection, SourcesMap, TChatInteraction } from "./types/index";
1110
import { OramaClient } from "@oramacloud/client";
1211
import { AnyOrama, Orama, SearchParams } from "@orama/orama";
1312
import { TThemeOverrides } from "./config/theme";
@@ -18,8 +17,7 @@ import { TThemeOverrides as TThemeOverrides1 } from "./components.d";
1817
import { SearchResultsProps } from "./components/internal/orama-search-results/orama-search-results";
1918
import { TextProps } from "./components/internal/orama-text/orama-text";
2019
export { ButtonProps } from "./components/internal/orama-button/orama-button";
21-
export { ChatMarkdownLinkHref, ChatMarkdownLinkTarget, ChatMarkdownLinkTitle, CloudIndexConfig, ColorScheme, Facet, OnAnswerGeneratedCallbackProps, OnAnswerSourceClickCallbackProps, OnChatMarkdownLinkClickedCallbackProps, OnSearchCompletedCallbackProps, OnSearchResultClickCallbackProps, onStartConversationCallbackProps, ResultItemRenderFunction, ResultMap, SearchResultBySection, SourcesMap } from "./types/index";
22-
export { TChatInteraction } from "./context/chatContext";
20+
export { ChatMarkdownLinkHref, ChatMarkdownLinkTarget, ChatMarkdownLinkTitle, CloudIndexConfig, ColorScheme, Facet, OnAnswerGeneratedCallbackProps, OnAnswerSourceClickCallbackProps, OnChatMarkdownLinkClickedCallbackProps, OnSearchCompletedCallbackProps, OnSearchResultClickCallbackProps, onStartConversationCallbackProps, ResultItemRenderFunction, ResultMap, SearchResultBySection, SourcesMap, TChatInteraction } from "./types/index";
2321
export { OramaClient } from "@oramacloud/client";
2422
export { AnyOrama, Orama, SearchParams } from "@orama/orama";
2523
export { TThemeOverrides } from "./config/theme";

0 commit comments

Comments
 (0)