new post type
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"main": ".vite/build/main.js",
|
||||
"types": "./src/types.d.ts",
|
||||
"scripts": {
|
||||
"dev": "rimraf .vite && WEB_API_URL=http://192.168.31.116:3000 electron-forge start",
|
||||
"dev": "rimraf .vite && WEB_API_URL=http://localhost:3000 electron-forge start",
|
||||
"start": "rimraf .vite && electron-forge start",
|
||||
"package": "rimraf .vite && electron-forge package",
|
||||
"make": "rimraf .vite && electron-forge make",
|
||||
|
||||
@@ -95,7 +95,7 @@ export class Client {
|
||||
}
|
||||
|
||||
createPost(params: {
|
||||
content?: string;
|
||||
metadata?: PostType["metadata"];
|
||||
targetType?: string;
|
||||
targetId?: string;
|
||||
}): Promise<PostType> {
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "./posts";
|
||||
export * from "./post-audio-player";
|
||||
export * from "./post-card";
|
||||
export * from "./post-actions";
|
||||
|
||||
59
enjoy/src/renderer/components/posts/post-actions.tsx
Normal file
59
enjoy/src/renderer/components/posts/post-actions.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { PostAudioPlayer } from "@renderer/components";
|
||||
import { Button } from "@renderer/components/ui";
|
||||
import { formatDateTime } from "@renderer/lib/utils";
|
||||
import { t } from "i18next";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import {
|
||||
DefaultVideoLayout,
|
||||
defaultLayoutIcons,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
import Markdown from "react-markdown";
|
||||
import { BotIcon, CheckIcon, CopyPlusIcon, PlusCircleIcon } from "lucide-react";
|
||||
import { useCopyToClipboard } from "@uidotdev/usehooks";
|
||||
|
||||
export const PostActions = (props: { post: PostType }) => {
|
||||
const { post } = props;
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
{post.target && post.targetType === "Medium" && (
|
||||
<Button variant="ghost" size="sm" className="px-1.5 rounded-full">
|
||||
<PlusCircleIcon
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("addToLibary")}
|
||||
className="w-5 h-5 text-muted-foreground hover:text-primary"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{typeof post.metadata?.content === "string" && (
|
||||
<Button variant="ghost" size="sm" className="px-1.5 rounded-full">
|
||||
{copied ? (
|
||||
<CheckIcon className="w-5 h-5 text-green-500" />
|
||||
) : (
|
||||
<CopyPlusIcon
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("copy")}
|
||||
className="w-5 h-5 text-muted-foreground hover:text-primary"
|
||||
onClick={() => {
|
||||
copyToClipboard(post.metadata.content as string);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 3000);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{post.metadata?.type === "prompt" && (
|
||||
<Button variant="ghost" size="sm" className="px-1.5 rounded-full">
|
||||
<BotIcon className="w-5 h-5 text-muted-foreground hover:text-primary" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
105
enjoy/src/renderer/components/posts/post-card.tsx
Normal file
105
enjoy/src/renderer/components/posts/post-card.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { PostAudioPlayer, PostActions } from "@renderer/components";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarImage,
|
||||
AvatarFallback,
|
||||
Button,
|
||||
} from "@renderer/components/ui";
|
||||
import { formatDateTime } from "@renderer/lib/utils";
|
||||
import { t } from "i18next";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import {
|
||||
DefaultVideoLayout,
|
||||
defaultLayoutIcons,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
export const PostCard = (props: { post: PostType }) => {
|
||||
const { post } = props;
|
||||
|
||||
return (
|
||||
<div className="rounded p-4 bg-white space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar>
|
||||
<AvatarImage src={post.user.avatarUrl} />
|
||||
<AvatarFallback className="text-xl">
|
||||
{post.user.name[0].toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="">{post.user.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatDateTime(post.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post.metadata?.type === "prompt" && (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("sharedPrompt")}
|
||||
</div>
|
||||
<Markdown className="prose prose-slate prose-pre:whitespace-normal select-text">
|
||||
{"```prompt\n" + post.metadata.content + "\n```"}
|
||||
</Markdown>
|
||||
</>
|
||||
)}
|
||||
|
||||
{post.targetType == "Medium" && (
|
||||
<PostMedium medium={post.target as MediumType} />
|
||||
)}
|
||||
|
||||
<PostActions post={post} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PostMedium = (props: { medium: MediumType }) => {
|
||||
const { medium } = props;
|
||||
if (!medium.sourceUrl) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{medium.mediumType == "Video" && (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("sharedAudio")}
|
||||
</div>
|
||||
<MediaPlayer
|
||||
poster={medium.coverUrl}
|
||||
src={{
|
||||
type: `${medium.mediumType.toLowerCase()}/${
|
||||
medium.extname.replace(".", "") || "mp4"
|
||||
}`,
|
||||
src: medium.sourceUrl,
|
||||
}}
|
||||
>
|
||||
<MediaProvider />
|
||||
<DefaultVideoLayout icons={defaultLayoutIcons} />
|
||||
</MediaPlayer>
|
||||
</>
|
||||
)}
|
||||
|
||||
{medium.mediumType == "Audio" && (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("sharedAudio")}
|
||||
</div>
|
||||
<PostAudioPlayer src={medium.sourceUrl} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{medium.coverUrl && medium.mediumType == "Audio" && (
|
||||
<div className="">
|
||||
<img src={medium.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PostOptions = (props: { post: PostType }) => {};
|
||||
@@ -1,14 +1,7 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { PostAudioPlayer } from "@renderer/components";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@renderer/components/ui";
|
||||
import { PostCard } from "@renderer/components";
|
||||
import { t } from "i18next";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import {
|
||||
DefaultVideoLayout,
|
||||
defaultLayoutIcons,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
import Markdown from "react-markdown";
|
||||
|
||||
export const Posts = () => {
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
@@ -43,63 +36,3 @@ export const Posts = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PostCard = (props: { post: PostType }) => {
|
||||
const { post } = props;
|
||||
|
||||
return (
|
||||
<div className="rounded p-4 bg-white">
|
||||
<div className="flex items-center mb-4 justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar>
|
||||
<AvatarImage src={post.user.avatarUrl} />
|
||||
<AvatarFallback className="text-xl">
|
||||
{post.user.name[0].toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="">{post.user.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
{post.content && (
|
||||
<Markdown className="prose select-text mb-4">{post.content}</Markdown>
|
||||
)}
|
||||
{post.targetType == "Medium" && <PostMedium medium={post.target} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PostMedium = (props: { medium: MediumType }) => {
|
||||
const { medium } = props;
|
||||
if (!medium.sourceUrl) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
{medium.mediumType == "Video" && (
|
||||
<MediaPlayer
|
||||
poster={medium.coverUrl}
|
||||
src={{
|
||||
type: `${medium.mediumType.toLowerCase()}/${
|
||||
medium.extname.replace(".", "") || "mp4"
|
||||
}`,
|
||||
src: medium.sourceUrl,
|
||||
}}
|
||||
>
|
||||
<MediaProvider />
|
||||
<DefaultVideoLayout icons={defaultLayoutIcons} />
|
||||
</MediaPlayer>
|
||||
)}
|
||||
|
||||
{medium.mediumType == "Audio" && (
|
||||
<PostAudioPlayer src={medium.sourceUrl} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{medium.coverUrl && medium.mediumType == "Audio" && (
|
||||
<div className="">
|
||||
<img src={medium.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -179,7 +179,10 @@ export default () => {
|
||||
const content = message.content;
|
||||
webApi
|
||||
.createPost({
|
||||
content,
|
||||
metadata: {
|
||||
type: "prompt",
|
||||
content,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
|
||||
25
enjoy/src/types.d.ts
vendored
25
enjoy/src/types.d.ts
vendored
@@ -105,31 +105,6 @@ type MeaningType = {
|
||||
lookups: LookupType[];
|
||||
};
|
||||
|
||||
type StoryType = {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
content: string;
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
vocabulary?: string[];
|
||||
extracted?: boolean;
|
||||
starred?: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
type CreateStoryParamsType = {
|
||||
title: string;
|
||||
content: string;
|
||||
url: string;
|
||||
html: string;
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
|
||||
type PagyResponseType = {
|
||||
page: number;
|
||||
next: number | null;
|
||||
|
||||
20
enjoy/src/types/post.d.ts
vendored
20
enjoy/src/types/post.d.ts
vendored
@@ -1,9 +1,17 @@
|
||||
type PostType = {
|
||||
id: string;
|
||||
content?: string;
|
||||
metadata: {
|
||||
type: 'text' | 'prompt' | 'llm_configuration';
|
||||
content:
|
||||
| string
|
||||
| {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
user: UserType;
|
||||
targetType: string;
|
||||
target?: MediumType;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
targetType?: string;
|
||||
targetId?: string;
|
||||
target?: MediumType | StoryType;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
24
enjoy/src/types/story.d.ts
vendored
Normal file
24
enjoy/src/types/story.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
type StoryType = {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
content: string;
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
vocabulary?: string[];
|
||||
extracted?: boolean;
|
||||
starred?: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
type CreateStoryParamsType = {
|
||||
title: string;
|
||||
content: string;
|
||||
url: string;
|
||||
html: string;
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user