Skip to content

Commit 62b20e0

Browse files
authored
Merge pull request #1 from cwandev/feature/actions
feat: add actions component
2 parents c0f4229 + e073bd8 commit 62b20e0

File tree

20 files changed

+630
-15
lines changed

20 files changed

+630
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ AI Elements Vue includes the following components:
8686
| `conversation` | ✅ 已完成 | Container for chat conversations |
8787
| `response` | ✅ 已完成 | Formatted AI response display |
8888
| `prompt-input` | ✅ 已完成 | Advanced input component with model selection |
89-
| `actions` | ❌ 未完成 | Interactive action buttons for AI responses |
89+
| `actions` | ✅ 已完成 | Interactive action buttons for AI responses |
9090
| `branch` | ❌ 未完成 | Branch visualization for conversation flows |
9191
| `code-block` | ❌ 未完成 | Syntax-highlighted code display with copy functionality |
9292
| `image` | ❌ 未完成 | AI-generated image display component |
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
import { Action, Actions } from '@repo/elements/actions'
3+
import { Message, MessageContent } from '@repo/elements/message'
4+
import { Copy, Heart, RefreshCcw, Share, ThumbsDown, ThumbsUp } from 'lucide-vue-next'
5+
import { ref } from 'vue'
6+
7+
const liked = ref(false)
8+
const disliked = ref(false)
9+
const favorited = ref(false)
10+
11+
const responseContent = `This is a response from an assistant.
12+
13+
Try hovering over this message to see the actions appear!`
14+
15+
function handleRetry() {
16+
// eslint-disable-next-line no-console
17+
console.log('Retrying request...')
18+
}
19+
20+
function handleCopy(content?: string) {
21+
// eslint-disable-next-line no-console
22+
console.log('Copied:', content)
23+
}
24+
25+
function handleShare(content?: string) {
26+
// eslint-disable-next-line no-console
27+
console.log('Sharing:', content)
28+
}
29+
30+
const actions = [
31+
{ icon: RefreshCcw, label: 'Retry', onClick: handleRetry },
32+
{ icon: ThumbsUp, label: 'Like', onClick: () => (liked.value = !liked.value) },
33+
{ icon: ThumbsDown, label: 'Dislike', onClick: () => (disliked.value = !disliked.value) },
34+
{ icon: Copy, label: 'Copy', onClick: () => handleCopy(responseContent) },
35+
{ icon: Share, label: 'Share', onClick: () => handleShare(responseContent) },
36+
{ icon: Heart, label: 'Favorite', onClick: () => (favorited.value = !favorited.value) },
37+
]
38+
</script>
39+
40+
<template>
41+
<Message class="group flex flex-col items-start gap-2" from="assistant">
42+
<MessageContent>{{ responseContent }}</MessageContent>
43+
<Actions class="mt-2 opacity-0 group-hover:opacity-100">
44+
<Action v-for="action in actions" :key="action.label" :label="action.label" @click="action.onClick">
45+
<component :is="action.icon" class="size-3" />
46+
</Action>
47+
</Actions>
48+
</Message>
49+
</template>

apps/test/app/examples/actions.vue

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script setup lang="ts">
2+
import { Action, Actions } from '@repo/elements/actions'
3+
import { Conversation, ConversationContent } from '@repo/elements/conversation'
4+
import { Message, MessageContent } from '@repo/elements/message'
5+
import { Copy, RefreshCcw, Share, ThumbsDown, ThumbsUp } from 'lucide-vue-next'
6+
import { nanoid } from 'nanoid'
7+
import { ref } from 'vue'
8+
9+
interface MsgItem {
10+
key: string
11+
from: 'user' | 'assistant'
12+
content: string
13+
avatar: string
14+
name: string
15+
}
16+
17+
const messages: MsgItem[] = [
18+
{
19+
key: nanoid(),
20+
from: 'user',
21+
content: 'Hello, how are you?',
22+
avatar: 'https://github.com/haydenbleasel.png',
23+
name: 'Hayden Bleasel',
24+
},
25+
{
26+
key: nanoid(),
27+
from: 'assistant',
28+
content: 'I am fine, thank you!',
29+
avatar: 'https://github.com/openai.png',
30+
name: 'OpenAI',
31+
},
32+
]
33+
34+
const liked = ref(false)
35+
const disliked = ref(false)
36+
37+
function handleRetry() {}
38+
function handleCopy() {}
39+
function handleShare() {}
40+
41+
const actions = [
42+
{ icon: RefreshCcw, label: 'Retry', onClick: handleRetry },
43+
{ icon: ThumbsUp, label: 'Like', onClick: () => (liked.value = !liked.value) },
44+
{ icon: ThumbsDown, label: 'Dislike', onClick: () => (disliked.value = !disliked.value) },
45+
{ icon: Copy, label: 'Copy', onClick: () => handleCopy() },
46+
{ icon: Share, label: 'Share', onClick: () => handleShare() },
47+
]
48+
</script>
49+
50+
<template>
51+
<Conversation class="relative w-full">
52+
<ConversationContent>
53+
<Message
54+
v-for="message in messages"
55+
:key="message.key"
56+
:from="message.from"
57+
class="flex flex-col gap-2"
58+
:class="message.from === 'assistant' ? 'items-start' : 'items-end'"
59+
>
60+
<MessageContent>{{ message.content }}</MessageContent>
61+
<Actions v-if="message.from === 'assistant'" class="mt-2">
62+
<Action v-for="action in actions" :key="action.label" :label="action.label" @click="action.onClick">
63+
<component :is="action.icon" class="size-4" />
64+
</Action>
65+
</Actions>
66+
</Message>
67+
</ConversationContent>
68+
</Conversation>
69+
</template>

apps/test/app/pages/index.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script setup lang="ts">
22
import { Card, CardContent, CardHeader, CardTitle } from '@repo/shadcn-vue/components/ui/card'
3+
import ActionsHover from '~/examples/actions-hover.vue'
4+
import Actions from '~/examples/actions.vue'
35
import Conversation from '~/examples/conversation.vue'
46
import MessageMarkdown from '~/examples/message-markdown.vue'
57
import Message from '~/examples/message.vue'
@@ -8,6 +10,8 @@ import Response from '~/examples/response.vue'
810
911
const components = [
1012
{ name: 'Message', Component: Message },
13+
{ name: 'Actions', Component: Actions },
14+
{ name: 'ActionsHover', Component: ActionsHover },
1115
{ name: 'PromptInput', Component: PromptInput },
1216
{ name: 'Conversation', Component: Conversation },
1317
{ name: 'Response', Component: Response },

apps/www/content/1.overview/1.Introduction.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ You can install it with:
2424
:::ComponentLoader{label="Response" componentName="Response"}
2525
:::
2626

27+
:::ComponentLoader{label="Actions" componentName="Actions"}
28+
:::
29+
2730
View the [source code](https://github.com/cwandev/ai-elements-vue) for all components on GitHub.

0 commit comments

Comments
 (0)