genetic-orange
genetic-orange•6mo ago

Live provider example

I am using django strawberry backend with graphql. I want to start a live provider from refine. Can you show me a simple example how to define and use a live provider with these components?
63 Replies
kapa.ai
kapa.ai•6mo ago
Hi @Valugar. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- I'm sorry, but the knowledge sources provided do not contain specific examples of using a live provider with a Django Strawberry backend and GraphQL in Refine. However, I can guide you on how to generally set up a live provider in Refine using GraphQL. Firstly, you need to create a live provider. Here is a general example of how you can do this:
import { Refine } from "@refinedev/core";
import { createClient } from "graphql-ws";

import { liveProvider } from "./liveProvider";

const gqlWebSocketClient = createClient({
url: "YOUR_WS_URL",
});

const App: React.FC = () => {
return (
<Refine liveProvider={liveProvider(gqlWebSocketClient)}>
{/* ... */}{" "}
</Refine>
);
};
import { Refine } from "@refinedev/core";
import { createClient } from "graphql-ws";

import { liveProvider } from "./liveProvider";

const gqlWebSocketClient = createClient({
url: "YOUR_WS_URL",
});

const App: React.FC = () => {
return (
<Refine liveProvider={liveProvider(gqlWebSocketClient)}>
{/* ... */}{" "}
</Refine>
);
};
(Source: Refine Documentation (https://refine.dev/docs/guides-concepts/realtime#usage-1)) In this example, liveProvider is imported from a local file and used in the Refine component. The createClient function from graphql-ws is used to create a GraphQL WebSocket client, which is then passed to the liveProvider. Please note that you need to replace "YOUR_WS_URL" with your actual WebSocket URL. Refine remains agnostic in its API to allow different solutions to be integrated. Therefore, you should be able to adapt this example to work with Django Strawberry and GraphQL. For more detailed information, you can refer to the Refine Live Provider documentation (https://refine.dev/docs/realtime/live-provider/). Remember to install the necessary packages for your project. For example, if you're using Ably for Realtime features, you would need to install the Ably live provider package from Refine:
npm i @refinedev/ably
npm i @refinedev/ably
(Source: Refine Documentation (https://refine.dev/docs/advanced-tutorials/real-time)) I hope this helps! If you have more specific questions or run into any issues, feel free to ask. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai I keep getting error: Argument 'input' of required type 'CreateLocationSubscriptionFilterInput!' was provided the variable '$input' which was not provided a runtime value. For this: @strawberry.input class CreateLocationSubscriptionFilterInput: gateway_id: Optional[str] timestamp: Optional[datetime] latitude: Optional[float]
longitude: Optional[float] @strawberry.input class UpdateLocationSubscriptionFilterInput: gateway: Optional[str] timestamp: Optional[datetime] latitude: Optional[float] longitude: Optional[float] @strawberry.input class DeleteLocationSubscriptionFilterInput: gateway: Optional[str] timestamp: Optional[datetime]
@strawberry.type class Subscription: @strawberry.subscription async def created_location(self, input: CreateLocationSubscriptionFilterInput) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
new_locations = await sync_to_async(Location.objects.filter, thread_sensitive=True)( **{k: v for k, v in vars(input).items() if v is not None} ) for location in new_locations: yield LocationType.from_django(location) And I am doing a simple live provider refine with: const { data: locations, isLoading: locationIsLoading } = useList({ resource: "locations", meta: { gqlQuery: LOCATIONS_LIST_QUERY }, pagination: { pageSize: 999 }, liveMode: "auto", }); and export const LOCATIONS_LIST_QUERY = gql query LocationsList( $filter: LocationFilter! $sorting: [LocationSort!] $paging: OffsetPaging! ) { locations(filter: $filter, sorting: $sorting, paging: $paging) { nodes { gateway { id serialNumber vehicle{ id make model events{ description createdAt } drivers{ id name } } } location timestamp } totalCount } } ;
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message you're seeing suggests that the input variable for your created_location subscription is not being provided a value at runtime. This means that when your subscription is being called, it's not receiving the necessary input argument. In your Refine useList hook, you're using liveMode: "auto", which means Refine will automatically subscribe to the created, updated, and deleted events for the locations resource. However, it seems like your backend expects an input argument for the created_location subscription, which is not being provided. To resolve this issue, you need to ensure that the input argument is provided when the created_location subscription is called. This could be done in your GraphQL query or in your Refine code where you're setting up the subscription. Unfortunately, the provided knowledge sources do not contain specific examples or code snippets that show how to provide an input argument for a subscription in Refine. For more detailed information, you can refer to the Refine Live Provider documentation (https://refine.dev/docs/realtime/live-provider/). If you're still having trouble, I would recommend reaching out to the Refine community or checking the Strawberry GraphQL documentation for more specific guidance on handling subscriptions with input arguments. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai I keep getting Argument 'input' of required type 'CreateLocationSubscriptionFilterInput!' was provided the variable '$input' which was not provided a runtime value. no matter how I modify my backend. @strawberry.input class CreateLocationSubscriptionFilterInput: filter: LocationFilter
@strawberry.input class UpdateLocationSubscriptionFilterInput: gateway: Optional[str] timestamp: Optional[datetime] latitude: Optional[float] longitude: Optional[float] @strawberry.input class DeleteLocationSubscriptionFilterInput: gateway: Optional[str] timestamp: Optional[datetime]
@strawberry.type class Subscription: @strawberry.subscription async def created_location(self, input: CreateLocationSubscriptionFilterInput) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
new_locations = await sync_to_async(Location.objects.filter, thread_sensitive=True)( **{k: v for k, v in vars(input).items() if v is not None} ) for location in new_locations: yield LocationType.from_django(location)
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message you're seeing suggests that the input variable for your created_location subscription is not being provided a value at runtime. This means that when your subscription is being called, it's not receiving the necessary input argument. In your Refine useList hook, you're using liveMode: "auto", which means Refine will automatically subscribe to the created, updated, and deleted events for the locations resource. However, it seems like your backend expects an input argument for the created_location subscription, which is not being provided. To resolve this issue, you need to ensure that the input argument is provided when the created_location subscription is called. This could be done in your GraphQL query or in your Refine code where you're setting up the subscription. Unfortunately, the provided knowledge sources do not contain specific examples or code snippets that show how to provide an input argument for a subscription in Refine. For more detailed information, you can refer to the Refine Live Provider documentation (https://refine.dev/docs/realtime/live-provider/). If you're still having trouble, I would recommend reaching out to the Refine community or checking the Strawberry GraphQL documentation for more specific guidance on handling subscriptions with input arguments. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Ok, so if I change the created_location to: @strawberry.type class Subscription: @strawberry.subscription async def created_location(self) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
new_locations = await sync_to_async(Location.objects.filter, thread_sensitive=True)( **{k: v for k, v in vars(input).items() if v is not None} ) for location in new_locations: yield LocationType.from_django(location) will this work?
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In the code you provided, you removed the input argument from the created_location subscription, but you're still trying to use input in the body of the function. This will result in an error because input is not defined. If you want to remove the input argument, you should also remove or modify the line where you're using input:
new_locations = await sync_to_async(Location.objects.filter, thread_sensitive=True)(
**{k: v for k, v in vars(input).items() if v is not None}
)
new_locations = await sync_to_async(Location.objects.filter, thread_sensitive=True)(
**{k: v for k, v in vars(input).items() if v is not None}
)
You need to replace this with a logic that doesn't rely on input. However, without knowing more about your application and what you're trying to achieve, it's hard to suggest a specific solution. If you want to fetch all new locations without any filter, you could potentially simplify this to:
new_locations = await sync_to_async(Location.objects.all, thread_sensitive=True)()
new_locations = await sync_to_async(Location.objects.all, thread_sensitive=True)()
This will fetch all locations from your database every 0.5 seconds. Please note that this could be inefficient if you have a large number of locations. Remember to always test your changes to ensure they work as expected. If you're still having trouble, I would recommend reaching out to the Refine community or checking the Strawberry GraphQL documentation for more specific guidance. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
genetic-orange
genetic-orange•6mo ago
@kapa.ai I am still very much confused. I have this in the frontend: const { data: locations, isLoading: locationIsLoading } = useList({ resource: "locations", meta: { gqlQuery: LOCATIONS_LIST_QUERY }, pagination: { pageSize: 999 }, liveMode: "auto", }); with export const LOCATIONS_LISTQUERY = gql query LocationsList( $filter: LocationFilter! $sorting: [LocationSort!] $paging: OffsetPaging! ) { locations(filter: $filter, sorting: $sorting, paging: $paging) { nodes { gateway { id serialNumber vehicle{ id make model events{ description createdAt } drivers{ id name } } } location timestamp } totalCount } } ; And over on the backend I set it as: @strawberry.input class LocationFilter: id: Optional[StringFilter] = None gateway: Optional[StringFilter] = None location: Optional[StringFilter] = None timestamp: Optional[DateTimeFilter] = None and: Optional[List['LocationFilter']] = strawberry.field(name="and", defaultfactory=list) or: Optional[List['LocationFilter']] = strawberry.field(name="or", default_factory=list) @strawberry.input class CreateLocationSubscriptionFilterInput: filter: LocationFilter @strawberry.type class Subscription: @strawberry.subscription async def created_location(self) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
new_locations = await sync_to_async(Location.objects.all, thread_sensitive=True)() for location in new_locations: yield LocationType.from_django(location) And I keep getting the error: "Variable '$input' expected value of type 'CreateLocationSubscriptionFilterInput' which cannot be used as an input type."
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message you're seeing suggests that the CreateLocationSubscriptionFilterInput type is not recognized as a valid input type in your GraphQL schema. This could be due to a few reasons: 1. The CreateLocationSubscriptionFilterInput type is not correctly defined or not included in your GraphQL schema. Make sure that the CreateLocationSubscriptionFilterInput type is correctly defined in your Strawberry schema and that it's included in the schema that your GraphQL server is using. 2. The CreateLocationSubscriptionFilterInput type is not used as an input type in any of your GraphQL operations. In your created_location subscription, you're not using the CreateLocationSubscriptionFilterInput type as an input. If you're not using this type as an input in any of your GraphQL operations, GraphQL might not recognize it as a valid input type. 3. There's a mismatch between the CreateLocationSubscriptionFilterInput type in your frontend and backend code. Make sure that the CreateLocationSubscriptionFilterInput type in your frontend code matches the CreateLocationSubscriptionFilterInput type in your backend code. Unfortunately, the provided knowledge sources do not contain specific examples or code snippets that show how to define and use the CreateLocationSubscriptionFilterInput type in Refine. For more detailed information, you can refer to the Refine Live Provider documentation (https://refine.dev/docs/realtime/live-provider/). If you're still having trouble, I would recommend reaching out to the Refine community or checking the Strawberry GraphQL documentation for more specific guidance on defining and using input types. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
genetic-orange
genetic-orange•6mo ago
@kapa.ai Ah I see. Now help me then implement the create/update/ and deletion subscription please. import strawberry from ..types import LocationType from ..models import Location from .schem_types import CreateLocationSubscriptionFilterInput, UpdateOneLocationSubscriptionFilterInput, DeleteOneLocationSubscriptionFilterInput from strawberry.types import Info from typing import AsyncGenerator from typing import Optional from asgiref.sync import sync_to_async import asyncio @strawberry.type class Subscription: @strawberry.subscription async def created_location(self, input: Optional[CreateLocationSubscriptionFilterInput] = None) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
query = Location.objects.all() if input: query = query.filter({k: v for k, v in vars(input).items() if v is not None}) print(query) location_event = await location_events.get() yield location_event @strawberry.subscription async def updated_one_location(self, input: Optional[UpdateOneLocationSubscriptionFilterInput] = None) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
query = Location.objects.all() print("updated_one_location", query) if input: query = query.filter(
{k: v for k, v in vars(input).items() if v is not None}) print(query) new_locations = await sync_to_async(list)(query) for location in new_locations: print(location) yield LocationType( location = location ) @strawberry.subscription async def deleted_one_location(self, input: Optional[DeleteOneLocationSubscriptionFilterInput] = None) -> AsyncGenerator[LocationType, None]: while True: await asyncio.sleep(0.5)
query = Location.objects.all() print("updated_one_location", query) if input: query = query.filter(**{k: v for k, v in vars(input).items() if v is not None}) print(query) new_locations = await sync_to_async(list)(query) for location in new_locations: print(location) yield LocationType( location = location ) pass
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Your backend code for the created_location, updated_one_location, and deleted_one_location subscriptions seems to be correctly set up. Each subscription is an asynchronous generator that yields new LocationType instances. The input argument is optional and is used to filter the locations that are returned. On the frontend, you can use Refine's useSubscription hook to subscribe to these events. Here's a general example of how you can do this:
import { useSubscription } from "@refinedev/core";

const { data } = useSubscription({
resource: "locations",
event: "created",
variables: { input: { /* your input values */ } },
});
import { useSubscription } from "@refinedev/core";

const { data } = useSubscription({
resource: "locations",
event: "created",
variables: { input: { /* your input values */ } },
});
In this example, useSubscription is used to subscribe to the created event for the locations resource. The variables prop is used to provide the input argument for your subscription. You should replace /* your input values */ with the actual values you want to use for the input argument. You can do the same for the updated and deleted events:
const { data: updatedData } = useSubscription({
resource: "locations",
event: "updated",
variables: { input: { /* your input values */ } },
});

const { data: deletedData } = useSubscription({
resource: "locations",
event: "deleted",
variables: { input: { /* your input values */ } },
});
const { data: updatedData } = useSubscription({
resource: "locations",
event: "updated",
variables: { input: { /* your input values */ } },
});

const { data: deletedData } = useSubscription({
resource: "locations",
event: "deleted",
variables: { input: { /* your input values */ } },
});
Please note that you need to replace /* your input values */ with the actual values you want to use for the input argument. Unfortunately, the provided knowledge sources do not contain specific examples or code snippets that show how to use the useSubscription hook in Refine. For more detailed information, you can refer to the Refine Live Provider documentation (https://refine.dev/docs/realtime/live-provider/). I hope this helps! If you have more specific questions or run into any issues, feel free to ask. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai How can I make a graphQL custom call, meaning i have this: export const LOCATION_GET_HISTORY = gql mutation LocationHistory($vehicle_id: String) { historicalLocations(vehicle_id: $vehicle_id) { gateway_id location timestamp vehicle_id history_id history_date history_type } } ; And I don't care about list/table, I just want to get the data from it. Should I use useCustom?
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, you can use the useCustom hook to make a custom GraphQL call. The useCustom hook allows you to send a request to a custom endpoint and handle the response. Here's an example of how you can use the useCustom hook with your mutation:
import { useCustom } from "@refinedev/core";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
variables: { vehicle_id: "your_vehicle_id" },
});
import { useCustom } from "@refinedev/core";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
variables: { vehicle_id: "your_vehicle_id" },
});
In this example, useCustom is used to send a request to the locations resource with the LOCATION_GET_HISTORY mutation. The variables prop is used to provide the vehicle_id argument for your mutation. You should replace "your_vehicle_id" with the actual ID you want to use. The useCustom hook returns an object that includes the data from the response and a mutate function that you can use to manually trigger the mutation. Please note that you need to import the LOCATION_GET_HISTORY mutation from the correct file. In this example, it's imported from a file named "./queries". For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai There is no variables in the useCustom.
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion. You're correct, the useCustom hook does not accept a variables prop. Instead, you can include the variables directly in the config prop. Here's how you can do it:
import { useCustom } from "@refinedev/core";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
variables: { vehicle_id: "your_vehicle_id" },
},
});
import { useCustom } from "@refinedev/core";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
variables: { vehicle_id: "your_vehicle_id" },
},
});
In this example, useCustom is used to send a request to the locations resource with the LOCATION_GET_HISTORY mutation. The variables are included in the config prop, which is used to provide the vehicle_id argument for your mutation. You should replace "your_vehicle_id" with the actual ID you want to use. The useCustom hook returns an object that includes the data from the response and a mutate function that you can use to manually trigger the mutation. Please note that you need to import the LOCATION_GET_HISTORY mutation from the correct file. In this example, it's imported from a file named "./queries". For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Still wrong. There is no config - variables.
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion earlier. You're correct, the useCustom hook does not accept a variables prop directly in the config. Instead, you can pass the variables directly to the payload prop in the config object. Here's how you can do it:
import { useCustom } from "@pankod/refine";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
payload: { vehicle_id: "your_vehicle_id" },
},
});
import { useCustom } from "@pankod/refine";
import { LOCATION_GET_HISTORY } from "./queries";

const { data, mutate } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
payload: { vehicle_id: "your_vehicle_id" },
},
});
In this example, useCustom is used to send a request to the locations resource with the LOCATION_GET_HISTORY mutation. The payload is included in the config prop, which is used to provide the vehicle_id argument for your mutation. You should replace "your_vehicle_id" with the actual ID you want to use. The useCustom hook returns an object that includes the data from the response and a mutate function that you can use to manually trigger the mutation. Please note that you need to import the LOCATION_GET_HISTORY mutation from the correct file. In this example, it's imported from a file named "./queries". For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai You are wrong again! You don't seem to know how useCustom is used.
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion earlier. You're correct, and I appreciate your patience. The useCustom hook in Refine is used to send custom query requests. It does not directly accept a variables prop. Instead, you can pass the variables as part of the url or method parameters. Here's how you can use the useCustom hook:
import { useCustom } from "@pankod/refine";

const { data, isLoading } = useCustom({
url: `your_endpoint_url?vehicle_id=your_vehicle_id`,
method: "get",
});
import { useCustom } from "@pankod/refine";

const { data, isLoading } = useCustom({
url: `your_endpoint_url?vehicle_id=your_vehicle_id`,
method: "get",
});
In this example, useCustom is used to send a GET request to the specified URL. The vehicle_id is included as a query parameter in the URL. You should replace your_endpoint_url and your_vehicle_id with the actual endpoint URL and vehicle ID you want to use. The useCustom hook returns an object that includes the data from the response and an isLoading flag that indicates whether the request is still in progress. Please note that the useCustom hook is an extended version of TanStack Query's useQuery and supports all features of the mutation but also adds some extra features. It uses the custom method as the query function from the dataProvider which is passed to <Refine>. For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/data/hooks/use-custom). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai How can I add variables to this call: const { data: responseData, isLoading: locationLoading } = useCustom({ meta: { gqlMutation: LOCATION_GET_HISTORY }, method: "get", url: ${API_URL}?vehicleId=${data.id}, variables: {
} }); This one doesn't seem to work
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion earlier. The useCustom hook in Refine does not directly accept a variables prop. Instead, you can pass the variables as part of the payload parameter in the config object. Here's how you can use the useCustom hook:
import { useCustom } from "@pankod/refine";

const { data: responseData, isLoading: locationLoading } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
method: "get",
payload: {
vehicleId: data.id,
},
},
});
import { useCustom } from "@pankod/refine";

const { data: responseData, isLoading: locationLoading } = useCustom({
resource: "locations",
meta: { gqlMutation: LOCATION_GET_HISTORY },
config: {
method: "get",
payload: {
vehicleId: data.id,
},
},
});
In this example, useCustom is used to send a GET request with the LOCATION_GET_HISTORY mutation. The vehicleId is included as part of the payload in the config object. The useCustom hook returns an object that includes the data from the response and an isLoading flag that indicates whether the request is still in progress. Please note that you need to import the LOCATION_GET_HISTORY mutation from the correct file. For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai It doesn't seem to work. Are there other ways to do this? maybe not using the useCustom?
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I apologize for the confusion earlier. If useCustom is not working as expected, you might want to consider using Apollo Client's hooks directly. Refine is built on top of Apollo Client, so you can use any of Apollo's hooks in your Refine application. Here's how you can use the useMutation hook from Apollo Client:
import { useMutation } from "@apollo/client";
import { LOCATION_GET_HISTORY } from "./queries";

const [getLocationHistory, { data, loading, error }] = useMutation(LOCATION_GET_HISTORY, {
variables: {
vehicle_id: "your_vehicle_id",
},
});

// Call the mutation when needed
getLocationHistory();
import { useMutation } from "@apollo/client";
import { LOCATION_GET_HISTORY } from "./queries";

const [getLocationHistory, { data, loading, error }] = useMutation(LOCATION_GET_HISTORY, {
variables: {
vehicle_id: "your_vehicle_id",
},
});

// Call the mutation when needed
getLocationHistory();
In this example, useMutation is used to create a function (getLocationHistory) that you can call to execute the mutation. The variables option is used to provide the vehicle_id argument for your mutation. You should replace "your_vehicle_id" with the actual ID you want to use. The useMutation hook returns an array that includes the mutation function and an object with the data, loading, and error from the mutation. Please note that you need to import the LOCATION_GET_HISTORY mutation from the correct file. For more detailed information, you can refer to the Apollo Client useMutation documentation (https://www.apollographql.com/docs/react/data/mutations/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@ka` @kapa.ai Here?
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- I'm sorry, but your message is not clear. Could you please provide more details or context? I'm here to help you with Refine, a React-based framework for building data-intensive applications. If you have any questions or issues related to Refine, feel free to ask! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai I need your help. I want to create a new resource page 'history' such that: { name: "vehicles", list: "/vehicles", create: "/vehicles/create", edit: "/vehicles/edit/:id", show: "/vehicles/show/:id", meta: { canDelete: true, icon: <CarOutlined/> }, }, will have also a history: "/vehicle/history/:id is it possible to add?
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, it is possible to add a new page to your resource. However, the history page you want to add is not a standard CRUD page, so you can't add it directly to the resource definition. Instead, you can define a new route for the history page in your application's routing configuration. Here's how you can do it:
import { Route } from "react-router-dom";
import { Refine } from "@pankod/refine";
import { CarOutlined } from "@ant-design/icons";

import { VehicleList, VehicleCreate, VehicleEdit, VehicleShow, VehicleHistory } from "pages/vehicles";

const App: React.FC = () => (
<Refine
resources={[
{
name: "vehicles",
list: VehicleList,
create: VehicleCreate,
edit: VehicleEdit,
show: VehicleShow,
meta: {
canDelete: true,
icon: <CarOutlined/>
},
},
]}
>
<Route path="/vehicles/history/:id" element={<VehicleHistory />} />
</Refine>
);
import { Route } from "react-router-dom";
import { Refine } from "@pankod/refine";
import { CarOutlined } from "@ant-design/icons";

import { VehicleList, VehicleCreate, VehicleEdit, VehicleShow, VehicleHistory } from "pages/vehicles";

const App: React.FC = () => (
<Refine
resources={[
{
name: "vehicles",
list: VehicleList,
create: VehicleCreate,
edit: VehicleEdit,
show: VehicleShow,
meta: {
canDelete: true,
icon: <CarOutlined/>
},
},
]}
>
<Route path="/vehicles/history/:id" element={<VehicleHistory />} />
</Refine>
);
In this example, a new Route component is added for the history page. The path prop is set to "/vehicles/history/:id", and the element prop is set to the VehicleHistory component. You should replace VehicleHistory with the actual component you want to use for the history page. Please note that this route will be added in addition to the routes defined by the resources prop in the Refine component. For more detailed information, you can refer to the Refine documentation (https://refine.dev/docs). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Ok good - so now I added the Router path: <Route path="/vehicles/history/:id" element={<HistoricalVehicleData />} /> and how can I then call navigate there with the button? <Button size="small" icon={<SearchOutlined />} title={"title"} className="refine-edit-button" onClick={() => { console.log(value) return go({ to: { resource: "vehicles", action: "history", id: value }, type: "replace", }); }} />
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You're on the right track. You can use the useNavigation hook from Refine to navigate to the new route. However, since the history page is not a standard CRUD action, you can't use the go function with the { resource: "vehicles", action: "history", id: value } object. Instead, you can use the go function with a string that represents the path to the history page. Here's how you can do it:
import { useNavigation } from "@pankod/refine";
import { Button } from "antd";
import { SearchOutlined } from "@ant-design/icons";

// ...

const { go } = useNavigation();

// ...

<Button
size="small"
icon={<SearchOutlined />}
title={"title"}
className="refine-edit-button"
onClick={() => {
console.log(value)
return go(`/vehicles/history/${value}`, "replace");
}}
/>
import { useNavigation } from "@pankod/refine";
import { Button } from "antd";
import { SearchOutlined } from "@ant-design/icons";

// ...

const { go } = useNavigation();

// ...

<Button
size="small"
icon={<SearchOutlined />}
title={"title"}
className="refine-edit-button"
onClick={() => {
console.log(value)
return go(`/vehicles/history/${value}`, "replace");
}}
/>
In this example, the go function is called with the path to the history page as a string. The value variable is included in the path to represent the id parameter. You should replace value with the actual ID you want to use. Please note that the second argument to the go function is "replace", which means that the current history entry will be replaced with the new one. For more detailed information, you can refer to the Refine useNavigation documentation (https://refine.dev/docs/api-reference/core/hooks/navigation/useNavigation/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Is there a way to force the query each time the page is loaded? const { data: responseData, isLoading: locationLoading } = useCustom({ meta: { gqlQuery: LOCATION_GET_HISTORY, variables: { id: vehicleId, } }, method: "get", url: ${API_URL}, });
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, you can force a query to run each time the page is loaded by using the enabled option in the queryOptions prop. You can set enabled to true to run the query immediately when the component mounts. Here's how you can do it:
import { useCustom } from "@pankod/refine";

const { data: responseData, isLoading: locationLoading } = useCustom({
meta: {
gqlQuery: LOCATION_GET_HISTORY,
variables: {
id: vehicleId,
}
},
method: "get",
url: `${API_URL}`,
queryOptions: {
enabled: true,
},
});
import { useCustom } from "@pankod/refine";

const { data: responseData, isLoading: locationLoading } = useCustom({
meta: {
gqlQuery: LOCATION_GET_HISTORY,
variables: {
id: vehicleId,
}
},
method: "get",
url: `${API_URL}`,
queryOptions: {
enabled: true,
},
});
In this example, useCustom is used to send a GET request with the LOCATION_GET_HISTORY query. The enabled option is set to true in the queryOptions prop, which means the query will run immediately when the component mounts. Please note that you need to import the LOCATION_GET_HISTORY query from the correct file. For more detailed information, you can refer to the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/) and the React Query useQuery documentation (https://react-query.tanstack.com/reference/useQuery). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai So it's still not working well.. The page seems to be refreshing multiple times while the data is being loaded.. maybe it's here? <Route path="/vehicles/history/:id" element={<HistoricalVehicleData />} />
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- If your page is refreshing multiple times while the data is being loaded, it might be due to the way your HistoricalVehicleData component is set up. It's hard to say for sure without seeing the code for that component. However, one common cause of this issue is having a state update inside a useEffect hook that doesn't have the correct dependency array. If you're using a useEffect hook to fetch data when the component mounts, make sure you include all the variables that the effect depends on in the dependency array. Here's a general example:
import React, {{ useEffect, useState }} from "react";
import {{ useCustom }} from "@pankod/refine";

const HistoricalVehicleData = () => {
const [vehicleId, setVehicleId] = useState(null);

const {{ data: responseData, isLoading: locationLoading }} = useCustom({{
meta: {{
gqlQuery: LOCATION_GET_HISTORY,
variables: {{
id: vehicleId,
}}
}},
method: "get",
url: `${{API_URL}}`,
queryOptions: {{
enabled: !!vehicleId,
}},
}});

useEffect(() => {
// Fetch the vehicle ID and update the state
const id = fetchVehicleId();
setVehicleId(id);
}, []); // The dependency array is empty, which means this effect runs once when the component mounts

// ...
};
import React, {{ useEffect, useState }} from "react";
import {{ useCustom }} from "@pankod/refine";

const HistoricalVehicleData = () => {
const [vehicleId, setVehicleId] = useState(null);

const {{ data: responseData, isLoading: locationLoading }} = useCustom({{
meta: {{
gqlQuery: LOCATION_GET_HISTORY,
variables: {{
id: vehicleId,
}}
}},
method: "get",
url: `${{API_URL}}`,
queryOptions: {{
enabled: !!vehicleId,
}},
}});

useEffect(() => {
// Fetch the vehicle ID and update the state
const id = fetchVehicleId();
setVehicleId(id);
}, []); // The dependency array is empty, which means this effect runs once when the component mounts

// ...
};
In this example, a useEffect hook is used to fetch the vehicle ID when the component mounts. The setVehicleId function is called to update the state with the fetched ID. The useCustom hook then uses this ID to fetch the historical data. The enabled option in the queryOptions prop is set to !!vehicleId, which means the query will run immediately when vehicleId is truthy. Please replace fetchVehicleId with the actual function you're using to fetch the vehicle ID. For more detailed information, you can refer to the React useEffect documentation (https://reactjs.org/docs/hooks-effect.html) and the Refine useCustom documentation (https://refine.dev/docs/api-reference/core/hooks/data/useCustom/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai I am trying to handle error during mutation. I am getting this error modal: rror: duplicate key value violates unique constraint "crm_gateway_serial_number_key" DETAIL: Key (serial_number)=(123123) already exists..: {"response":{"data":null,"errors":[{"message":"Error: duplicate key value violates unique constraint "crm_gateway_serial_number_key"\nDETAIL: Key (serial_number)=(123123) already exists..","locations":[{"line":2,"column":7}],"path":["createOneGateway"]}],"status":200,"headers":{"access-control-allow-credentials":"true","access-control- ... which is a long message I get from the backend. I was wondering how to handle it to show a proper message on the frontend? I have this: export const GatewayCreatePage: React.FC<PropsWithChildren> = ({ children, }) => { const { list, replace } = useNavigation(); const { pathname } = useLocation(); const [searchParams] = useSearchParams(); const { formProps, saveButtonProps, onFinish } = useForm({ onMutationError: (data, variables, context, isAutoSave) => { console.log({ data, variables, context, isAutoSave }); }, redirect: "list", }); and then the return: <Form layout="vertical" {...formProps} onFinish={(values) => { onFinish({ ...values }); }} > ...
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- You can handle server-side validation errors in Refine by using the onError callback in the useForm hook. This callback is called when a mutation fails. You can use it to display a custom error message based on the error you get from the server. Here's how you can do it:
import { useForm } from "@pankod/refine";
import { notification } from "antd";

// ...

const { formProps, saveButtonProps, onFinish } = useForm({
onMutationError: (error) => {
console.log(error); // Log the error for debugging

// Extract the error message
const errorMessage = error?.message || "An unknown error occurred";

// Display a notification with the error message
notification.error({
message: "Failed to create gateway",
description: errorMessage,
});
},
redirect: "list",
});

// ...

<Form
layout="vertical"
{...formProps}
onFinish={(values) => {
onFinish({ ...values });
}}
>
import { useForm } from "@pankod/refine";
import { notification } from "antd";

// ...

const { formProps, saveButtonProps, onFinish } = useForm({
onMutationError: (error) => {
console.log(error); // Log the error for debugging

// Extract the error message
const errorMessage = error?.message || "An unknown error occurred";

// Display a notification with the error message
notification.error({
message: "Failed to create gateway",
description: errorMessage,
});
},
redirect: "list",
});

// ...

<Form
layout="vertical"
{...formProps}
onFinish={(values) => {
onFinish({ ...values });
}}
>
In this example, the onMutationError callback is used to display a notification with the error message when a mutation fails. The error object that's passed to the callback includes a message property that contains the error message from the server. Please note that you need to import the notification component from Ant Design to display the notification. For more detailed information, you can refer to the Refine useForm documentation (https://refine.dev/docs/api-reference/core/hooks/form/useForm/) and the Ant Design notification documentation (https://ant.design/components/notification/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Ok now I try to have a proper filter: const { selectProps } = useSelect<ICategory>({ resource: "reports", optionLabel: "reportType", }); console.log(selectProps) and I have this useTable: const { tableProps, searchFormProps, setCurrent, setPageSize, filters, sorters, setFilters, tableQueryResult, } = useTable< GetFieldsFromList<ReportsListQuery>, HttpError, { name: string } >({ resource: "reports", pagination: { pageSize: 12, }, sorters: { initial: [ { field: "created_at", order: "asc", }, ], }, onSearch: (values) => { return [ { field: "reportType", operator: "contains", value: values.name, }, ]; }, meta: { gqlQuery: REPORTS_LISTQUERY, }, }); and I try to have a dropdown filter: <Table.Column dataIndex="reportType" title="Report Type" width={200} defaultFilteredValue={getDefaultFilter("reportType", filters, "in")} defaultSortOrder={getDefaultSortOrder("reportType", sorters)} filterDropdown={(props) => ( <FilterDropdown {...props} mapValue={(selectedKeys) => selectedKeys.map((i) => Number.parseInt(i.toString())) } > <Select style={{ minWidth: 200 }} mode="multiple" {...selectProps} /> </FilterDropdown> )} render={(, record: Report) => { return ( <Space> <Text>{ReportTypeEnum[record.reportType as keyof typeof ReportTypeEnum]}</Text> </Space> ); }} />
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- It seems like you're trying to create a dropdown filter for the reportType field in your table. Your current setup looks correct, but there are a few things you might want to check: 1. Make sure the useSelect hook is fetching the correct data. The resource prop should be set to the resource that contains the options for the dropdown. In your case, it's set to "reports". If the options for the dropdown are stored in a different resource, you should update this prop accordingly. 2. Check the optionLabel prop in the useSelect hook. It should be set to the field that contains the label for each option in the dropdown. In your case, it's set to "reportType". If the labels are stored in a different field, you should update this prop accordingly. 3. Make sure the mapValue function in the FilterDropdown component is correctly converting the selected keys to numbers. If the keys are already numbers, you might not need to use Number.parseInt. 4. Check the onSearch function in the useTable hook. It should return an array of filters based on the search values. In your case, it's creating a filter for the reportType field with the "contains" operator. If you want to filter by exact match instead, you should use the "eq" operator. Here's how you can update the onSearch function to filter by exact match:
onSearch: (values) => {
return [
{
field: "reportType",
operator: "eq",
value: values.name,
},
];
},
onSearch: (values) => {
return [
{
field: "reportType",
operator: "eq",
value: values.name,
},
];
},
For more detailed information, you can refer to the Refine useTable documentation (https://refine.dev/docs/api-reference/core/hooks/data/useTable/), the Refine useSelect documentation (https://refine.dev/docs/api-reference/core/hooks/data/useSelect/), and the Refine FilterDropdown documentation (https://refine.dev/docs/ui-integrations/ant-design/components/filter-dropdown/). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai No, the useSelects doesn't seem to return anything const { selectProps } = useSelect<ICategory>({ resource: "reports", }); console.log(selectProps) the print it: {options: Array(0), loading: false, showSearch: true, filterOption: false, onSearch: Æ’}
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The useSelect hook is used to manage a select component when the records in a resource need to be used as select options. It fetches data using the useList hook. If useSelect is not returning any options, it might be because there are no records in the specified resource, or the optionLabel and optionValue props are not set correctly. In your case, you're fetching data from the "reports" resource, but you haven't specified the optionLabel and optionValue props. These props should be set to the fields that contain the label and value for each option in the dropdown. Here's how you can update your useSelect hook:
const { selectProps } = useSelect<ICategory>({
resource: "reports",
optionLabel: "labelField", // Replace "labelField" with the actual field
optionValue: "valueField", // Replace "valueField" with the actual field
});
console.log(selectProps);
const { selectProps } = useSelect<ICategory>({
resource: "reports",
optionLabel: "labelField", // Replace "labelField" with the actual field
optionValue: "valueField", // Replace "valueField" with the actual field
});
console.log(selectProps);
In this example, optionLabel is set to "labelField" and optionValue is set to "valueField". You should replace "labelField" and "valueField" with the actual fields that contain the label and value for each option in the dropdown. If you're still not getting any options, you might want to check the data in your "reports" resource to make sure it contains the necessary records. For more detailed information, you can refer to the Refine useSelect documentation (https://refine.dev/docs/data/hooks/use-select). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai Something is not working well, I believe the issue is that there are no records in the specified resource. Even when running the simple const { selectProps:reportProps } = useSelect({ resource: "reports", }); the reportProps is empty. I do have this: const { tableProps, searchFormProps, setCurrent, setPageSize, filters, sorters, setFilters, tableQueryResult, } = useTable< GetFieldsFromList<ReportsListQuery>, HttpError, { name: string } >({ resource: "reports", pagination: { pageSize: 12, }, initialSorter: [ { field: "created_at", order: "asc", }, ], onSearch: (values) => { return [ { field: "reportType", operator: "contains", value: values.name, }, ]; }, meta: { gqlQuery: REPORTS_LIST_QUERY, }, }); That I thought it sets values in reports (as I wrote the 'reports' in resource)
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The useTable and useSelect hooks both fetch data from the specified resource, but they do not share this data between each other. The useTable hook fetches data for the table, and the useSelect hook fetches data for the select component. They work independently and do not affect each other. If useSelect is not returning any options, it might be because there are no records in the "reports" resource, or the API request is failing for some reason. Here are a few things you can check: 1. Check the data in your "reports" resource. Make sure it contains the necessary records. You can do this by checking your database or using an API testing tool like Postman to send a request to your API. 2. Check the network request made by useSelect. You can do this by opening the Network tab in your browser's developer tools and looking for the request made by useSelect. Check the request URL, method, headers, and body to make sure they're correct. Also, check the response to see if it contains the expected data. 3. Check for errors. If the API request is failing, useSelect should return an error object. You can log this object to see what the error is. Here's how you can do it:
const { selectProps, error } = useSelect({
resource: "reports",
});

console.log(error);
const { selectProps, error } = useSelect({
resource: "reports",
});

console.log(error);
If you're still having trouble, you might want to consider reaching out to the Refine community or the support team for further assistance. For more detailed information, you can refer to the Refine useSelect documentation (https://refine.dev/docs/data/hooks/use-select). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below
genetic-orange
genetic-orange•6mo ago
@kapa.ai so looks like the useSelect keeps returning the error: TypeError: Cannot read properties of undefined (reading 'length')
kapa.ai
kapa.ai•6mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message "TypeError: Cannot read properties of undefined (reading 'length')" typically occurs when you're trying to access the length property of an object that is undefined. In the context of useSelect, this error might occur if the data returned from the API is not in the expected format. The useSelect hook expects the data to be an array of objects, where each object represents a record in the resource. If the data is undefined or not an array, you might get this error. Here are a few things you can check: 1. Check the response from the API. Make sure it's returning an array of objects. You can do this by checking the network request made by useSelect in your browser's developer tools. 2. Check the data returned by useSelect. You can log this data to see what it is. Here's how you can do it:
const {{ selectProps, data }} = useSelect({{
resource: "reports",
}});

console.log(data);
const {{ selectProps, data }} = useSelect({{
resource: "reports",
}});

console.log(data);
3. Check your data provider. The data provider is responsible for making API requests and formatting the response data. Make sure it's correctly implemented for the getList method, which is used by useSelect. If you're still having trouble, you might want to consider reaching out to the Refine community or the support team for further assistance. For more detailed information, you can refer to the Refine useSelect documentation (https://refine.dev/docs/data/hooks/use-select). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai•6mo ago
<@240371095444652032> kapa.ai is still learning and improving, please let us know how it did by reacting below