@copilotkit/vue 2.0.0-rc.19 → 2.0.0-rc.20

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.
Files changed (73) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.mjs +2 -2
  3. package/dist/use-render-activity-message-CsWYyd6r.cjs +91 -0
  4. package/dist/use-render-activity-message-CsWYyd6r.cjs.map +1 -0
  5. package/dist/{use-render-activity-message-g7xEf4ep.js → use-render-activity-message-DntKKNdd.js} +6354 -6747
  6. package/dist/use-render-activity-message-DntKKNdd.js.map +1 -0
  7. package/dist/v2/components/InlineFeatureWarning.vue.d.ts.map +1 -1
  8. package/dist/v2/components/LicenseWarningBanner.vue.d.ts +2 -2
  9. package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
  10. package/dist/v2/components/chat/CopilotChat.vue.d.ts +1 -1
  11. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  12. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts +1 -1
  13. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -1
  14. package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts.map +1 -1
  15. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  16. package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
  17. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -1
  18. package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts.map +1 -1
  19. package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -1
  20. package/dist/v2/components/chat/CopilotPopup.vue.d.ts +1 -1
  21. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +1 -1
  22. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +1 -1
  23. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +1 -1
  24. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +1 -1
  25. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +1 -1
  26. package/dist/v2/components/chat/index.d.ts +12 -12
  27. package/dist/v2/components/chat/inspector-shortcuts.d.ts +2 -1
  28. package/dist/v2/components/chat/inspector-shortcuts.d.ts.map +1 -1
  29. package/dist/v2/components/chat/message-feedback.d.ts +18 -49
  30. package/dist/v2/components/chat/message-feedback.d.ts.map +1 -1
  31. package/dist/v2/components/chat/normalize-auto-scroll.d.ts +2 -2
  32. package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +1 -1
  33. package/dist/v2/components/chat/use-copy-feedback.d.ts +11 -0
  34. package/dist/v2/components/chat/use-copy-feedback.d.ts.map +1 -0
  35. package/dist/v2/hooks/use-keyboard-height.d.ts +1 -1
  36. package/dist/v2/hooks/use-keyboard-height.d.ts.map +1 -1
  37. package/dist/v2/hooks/use-pin-to-send.d.ts +4 -12
  38. package/dist/v2/hooks/use-pin-to-send.d.ts.map +1 -1
  39. package/dist/v2/index.cjs +1 -1
  40. package/dist/v2/index.mjs +1 -1
  41. package/dist/v2/lib/is-mobile-viewport.d.ts +1 -14
  42. package/dist/v2/lib/is-mobile-viewport.d.ts.map +1 -1
  43. package/dist/v2/lib/shallow-stable.d.ts.map +1 -1
  44. package/package.json +8 -8
  45. package/src/v2/components/InlineFeatureWarning.vue +20 -21
  46. package/src/v2/components/LicenseWarningBanner.vue +25 -149
  47. package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +48 -8
  48. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +19 -43
  49. package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +2 -2
  50. package/src/v2/components/chat/CopilotChatInspectorButton.vue +45 -108
  51. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +2 -28
  52. package/src/v2/components/chat/CopilotChatUserMessage.vue +24 -112
  53. package/src/v2/components/chat/CopilotChatWorkSummary.vue +23 -59
  54. package/src/v2/components/chat/CopilotModalHeader.vue +11 -23
  55. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.learningFeedback.test.ts +5 -5
  56. package/src/v2/components/chat/inspector-shortcuts.ts +10 -16
  57. package/src/v2/components/chat/message-feedback.ts +62 -197
  58. package/src/v2/components/chat/normalize-auto-scroll.ts +3 -17
  59. package/src/v2/components/chat/use-copy-feedback.ts +21 -0
  60. package/src/v2/hooks/use-keyboard-height.ts +17 -40
  61. package/src/v2/hooks/use-pin-to-send.ts +15 -99
  62. package/src/v2/lib/__tests__/shallow-stable.test.ts +54 -0
  63. package/src/v2/lib/is-mobile-viewport.ts +5 -23
  64. package/src/v2/lib/shallow-stable.ts +2 -16
  65. package/dist/use-render-activity-message-IH90zF01.cjs +0 -92
  66. package/dist/use-render-activity-message-IH90zF01.cjs.map +0 -1
  67. package/dist/use-render-activity-message-g7xEf4ep.js.map +0 -1
  68. package/dist/v2/lib/message-feedback.d.ts +0 -33
  69. package/dist/v2/lib/message-feedback.d.ts.map +0 -1
  70. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +0 -39
  71. package/src/v2/lib/__tests__/is-mobile-viewport.test.ts +0 -48
  72. package/src/v2/lib/__tests__/message-feedback.test.ts +0 -152
  73. package/src/v2/lib/message-feedback.ts +0 -120
@@ -1,33 +0,0 @@
1
- import type { AssistantMessage } from "@ag-ui/core";
2
- import type { CopilotKitCore } from "@copilotkit/core";
3
- /** Trajectory event name for a rating of one assistant message. */
4
- export declare const MESSAGE_FEEDBACK_EVENT_NAME = "message.feedback";
5
- /** Longest comment kept, in characters; the rest is cut so the event stays small. */
6
- export declare const MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH = 1000;
7
- export type MessageFeedbackRating = "up" | "down";
8
- /** The rating this page gave one assistant message. */
9
- export interface MessageFeedback {
10
- readonly rating: MessageFeedbackRating;
11
- readonly comment?: string;
12
- }
13
- /** The rating this page gave one message, if any. */
14
- export declare function getMessageFeedback(agentId: string, threadId: string, messageId: string): MessageFeedback | undefined;
15
- /** Calls `listener` whenever a rating in this Thread changes. */
16
- export declare function subscribeToMessageFeedback(agentId: string, threadId: string, listener: () => void): () => void;
17
- /**
18
- * Sets (or, with `null`, clears) this page's rating of one message and sends
19
- * it to the active Trajectory. Every change is a new event; the newest event
20
- * for a `feedbackId` is the current one. Like any Trajectory event, a change
21
- * made while no Trajectory captures, for example during a reconnect, is not
22
- * sent; the page still shows it.
23
- */
24
- export declare function setMessageFeedback(params: {
25
- copilotkit: CopilotKitCore;
26
- agentId: string;
27
- threadId: string;
28
- message: AssistantMessage;
29
- feedback: MessageFeedback | null;
30
- }): void;
31
- /** @internal Forgets every rating, so each test starts empty. */
32
- export declare function ɵresetMessageFeedbackCache(): void;
33
- //# sourceMappingURL=message-feedback.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-feedback.d.ts","sourceRoot":"","sources":["../../../src/v2/lib/message-feedback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,mEAAmE;AACnE,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAE9D,qFAAqF;AACrF,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAKxD,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAAC;AAElD,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2BD,qDAAqD;AACrD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,eAAe,GAAG,SAAS,CAE7B;AAED,iEAAiE;AACjE,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,IAAI,GACnB,MAAM,IAAI,CAYZ;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAClC,GAAG,IAAI,CA4BP;AAED,iEAAiE;AACjE,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -1,39 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- normalizeAutoScroll,
4
- type AutoScrollMode,
5
- } from "../normalize-auto-scroll";
6
-
7
- // Strict counterpart of React `normalize-auto-scroll.test.ts`.
8
- // Keep case order and wording aligned 1:1 with the React suite.
9
- describe("normalizeAutoScroll", () => {
10
- it("returns 'pin-to-bottom' for undefined (default)", () => {
11
- expect(normalizeAutoScroll(undefined)).toBe("pin-to-bottom");
12
- });
13
-
14
- it("maps true -> 'pin-to-bottom'", () => {
15
- expect(normalizeAutoScroll(true)).toBe("pin-to-bottom");
16
- });
17
-
18
- it("maps false -> 'none'", () => {
19
- expect(normalizeAutoScroll(false)).toBe("none");
20
- });
21
-
22
- it("passes 'pin-to-bottom' through", () => {
23
- expect(normalizeAutoScroll("pin-to-bottom")).toBe("pin-to-bottom");
24
- });
25
-
26
- it("passes 'pin-to-send' through", () => {
27
- expect(normalizeAutoScroll("pin-to-send")).toBe("pin-to-send");
28
- });
29
-
30
- it("passes 'none' through", () => {
31
- expect(normalizeAutoScroll("none")).toBe("none");
32
- });
33
-
34
- it("falls back to 'pin-to-bottom' for unknown strings", () => {
35
- expect(normalizeAutoScroll("bogus" as AutoScrollMode)).toBe(
36
- "pin-to-bottom",
37
- );
38
- });
39
- });
@@ -1,48 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { isMobileViewport } from "../is-mobile-viewport";
3
-
4
- describe("isMobileViewport", () => {
5
- const originalMatchMedia = window.matchMedia;
6
-
7
- afterEach(() => {
8
- window.matchMedia = originalMatchMedia;
9
- vi.restoreAllMocks();
10
- });
11
-
12
- it("returns false when window.matchMedia is undefined", () => {
13
- // @ts-expect-error - simulating an environment without matchMedia support.
14
- delete window.matchMedia;
15
-
16
- expect(isMobileViewport()).toBe(false);
17
- });
18
-
19
- it("returns true when the mobile media query matches", () => {
20
- window.matchMedia = vi.fn().mockReturnValue({
21
- matches: true,
22
- media: "(max-width: 767px)",
23
- onchange: null,
24
- addEventListener: vi.fn(),
25
- removeEventListener: vi.fn(),
26
- addListener: vi.fn(),
27
- removeListener: vi.fn(),
28
- dispatchEvent: vi.fn(),
29
- } as unknown as MediaQueryList);
30
-
31
- expect(isMobileViewport()).toBe(true);
32
- });
33
-
34
- it("returns false when the mobile media query does not match", () => {
35
- window.matchMedia = vi.fn().mockReturnValue({
36
- matches: false,
37
- media: "(max-width: 767px)",
38
- onchange: null,
39
- addEventListener: vi.fn(),
40
- removeEventListener: vi.fn(),
41
- addListener: vi.fn(),
42
- removeListener: vi.fn(),
43
- dispatchEvent: vi.fn(),
44
- } as unknown as MediaQueryList);
45
-
46
- expect(isMobileViewport()).toBe(false);
47
- });
48
- });
@@ -1,152 +0,0 @@
1
- import { CopilotKitCore } from "@copilotkit/core";
2
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import {
4
- getMessageFeedback,
5
- subscribeToMessageFeedback,
6
- MESSAGE_FEEDBACK_EVENT_NAME,
7
- setMessageFeedback,
8
- ɵresetMessageFeedbackCache,
9
- } from "../message-feedback";
10
-
11
- const emoji = "👍";
12
-
13
- /** A Core that records what it emits, with its agent-text choice stubbed. */
14
- function capturingCore({ agentText = true } = {}) {
15
- const copilotkit = new CopilotKitCore({});
16
- vi.spyOn(copilotkit, "ɵlearningCapturesAgentText", "get").mockReturnValue(
17
- agentText,
18
- );
19
- const emit = vi
20
- .spyOn(copilotkit, "emitTrajectoryEvent")
21
- .mockImplementation(() => {});
22
- return { copilotkit, emit };
23
- }
24
-
25
- const reply = { id: "m1", role: "assistant" as const, content: "Approved." };
26
-
27
- const rate = (
28
- copilotkit: CopilotKitCore,
29
- feedback: { rating: "up" | "down"; comment?: string } | null,
30
- message: { id: string; role: "assistant"; content: string } = reply,
31
- ) =>
32
- setMessageFeedback({
33
- copilotkit,
34
- agentId: "agent",
35
- threadId: "thread",
36
- message,
37
- feedback,
38
- });
39
-
40
- describe("message feedback store", () => {
41
- beforeEach(() => ɵresetMessageFeedbackCache());
42
- afterEach(() => vi.restoreAllMocks());
43
-
44
- it("cuts long text without splitting a character", () => {
45
- const { copilotkit, emit } = capturingCore();
46
-
47
- rate(
48
- copilotkit,
49
- { rating: "up", comment: emoji.repeat(1200) },
50
- { id: "m1", role: "assistant", content: emoji.repeat(600) },
51
- );
52
-
53
- const [name, value] = emit.mock.calls[0]!;
54
- expect(name).toBe(MESSAGE_FEEDBACK_EVENT_NAME);
55
- expect(value).toMatchObject({
56
- response: emoji.repeat(500),
57
- comment: emoji.repeat(1000),
58
- });
59
- });
60
-
61
- it("keeps the rating and sends each change, a removal as null", () => {
62
- const { copilotkit, emit } = capturingCore();
63
-
64
- rate(copilotkit, { rating: "up" });
65
- expect(getMessageFeedback("agent", "thread", "m1")).toEqual({
66
- rating: "up",
67
- });
68
- rate(copilotkit, null);
69
-
70
- expect(getMessageFeedback("agent", "thread", "m1")).toBeUndefined();
71
- expect(emit.mock.calls.map(([, value]) => value)).toEqual([
72
- expect.objectContaining({ feedbackId: "thread:m1", rating: "up" }),
73
- expect.objectContaining({ feedbackId: "thread:m1", rating: null }),
74
- ]);
75
- });
76
-
77
- it("trims a comment and drops a blank one", () => {
78
- const { copilotkit } = capturingCore();
79
-
80
- rate(copilotkit, { rating: "down", comment: " " });
81
- expect(getMessageFeedback("agent", "thread", "m1")).toEqual({
82
- rating: "down",
83
- });
84
- rate(copilotkit, { rating: "down", comment: " padded " });
85
-
86
- expect(getMessageFeedback("agent", "thread", "m1")).toEqual({
87
- rating: "down",
88
- comment: "padded",
89
- });
90
- });
91
-
92
- it("tells only the rated Thread's listeners", () => {
93
- const { copilotkit } = capturingCore();
94
- const rated = vi.fn();
95
- const other = vi.fn();
96
- subscribeToMessageFeedback("agent", "thread", rated);
97
- subscribeToMessageFeedback("agent", "other", other);
98
-
99
- rate(copilotkit, { rating: "up" });
100
-
101
- expect(rated).toHaveBeenCalledTimes(1);
102
- expect(other).not.toHaveBeenCalled();
103
- });
104
-
105
- it("keeps the object of a rating that did not change", () => {
106
- const { copilotkit } = capturingCore();
107
- rate(copilotkit, { rating: "up" });
108
- const before = getMessageFeedback("agent", "thread", "m1");
109
-
110
- rate(
111
- copilotkit,
112
- { rating: "down" },
113
- { id: "m2", role: "assistant", content: "No." },
114
- );
115
-
116
- expect(getMessageFeedback("agent", "thread", "m1")).toBe(before);
117
- });
118
-
119
- it("treats message ids like object keys as plain ids", () => {
120
- const { copilotkit } = capturingCore();
121
-
122
- rate(
123
- copilotkit,
124
- { rating: "up" },
125
- { id: "__proto__", role: "assistant", content: "" },
126
- );
127
-
128
- expect(getMessageFeedback("agent", "thread", "__proto__")).toEqual({
129
- rating: "up",
130
- });
131
- expect(
132
- getMessageFeedback("agent", "thread", "constructor"),
133
- ).toBeUndefined();
134
- });
135
-
136
- it("leaves out the reply text when capture omits agent text", () => {
137
- const { copilotkit, emit } = capturingCore({ agentText: false });
138
-
139
- rate(copilotkit, { rating: "up" });
140
-
141
- expect(emit.mock.calls[0]![1]).not.toHaveProperty("response");
142
- });
143
-
144
- it("names the reply's run when Core knows it", () => {
145
- const { copilotkit, emit } = capturingCore();
146
- vi.spyOn(copilotkit, "getRunIdForMessage").mockReturnValue("run-9");
147
-
148
- rate(copilotkit, { rating: "up" });
149
-
150
- expect(emit.mock.calls[0]![1]).toMatchObject({ runId: "run-9" });
151
- });
152
- });
@@ -1,120 +0,0 @@
1
- import type { AssistantMessage } from "@ag-ui/core";
2
- import type { CopilotKitCore } from "@copilotkit/core";
3
-
4
- /** Trajectory event name for a rating of one assistant message. */
5
- export const MESSAGE_FEEDBACK_EVENT_NAME = "message.feedback";
6
-
7
- /** Longest comment kept, in characters; the rest is cut so the event stays small. */
8
- export const MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH = 1000;
9
-
10
- /** Longest excerpt of the rated response sent with the rating, in characters. */
11
- const RESPONSE_EXCERPT_LENGTH = 500;
12
-
13
- export type MessageFeedbackRating = "up" | "down";
14
-
15
- /** The rating this page gave one assistant message. */
16
- export interface MessageFeedback {
17
- readonly rating: MessageFeedbackRating;
18
- readonly comment?: string;
19
- }
20
-
21
- // Ratings given on this page, by message. Kept outside Vue, so a rated
22
- // message that scrolls out of a virtual list still shows its rating when it
23
- // comes back. Intelligence holds the ratings across page loads.
24
- const ratings = new Map<string, MessageFeedback>();
25
- const listeners = new Map<string, Set<() => void>>();
26
-
27
- const threadKey = (agentId: string, threadId: string) =>
28
- JSON.stringify([agentId, threadId]);
29
-
30
- const messageKey = (agentId: string, threadId: string, messageId: string) =>
31
- JSON.stringify([agentId, threadId, messageId]);
32
-
33
- /** Cuts `text` to `max` characters without splitting a surrogate pair. */
34
- const clip = (text: string, max: number): string => {
35
- const characters = Array.from(text);
36
- return characters.length > max ? characters.slice(0, max).join("") : text;
37
- };
38
-
39
- /** The comment as kept and sent: trimmed and capped, or none. */
40
- const normalizeComment = (comment: unknown): string | undefined => {
41
- if (typeof comment !== "string") return undefined;
42
- const text = clip(comment.trim(), MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH);
43
- return text === "" ? undefined : text;
44
- };
45
-
46
- /** The rating this page gave one message, if any. */
47
- export function getMessageFeedback(
48
- agentId: string,
49
- threadId: string,
50
- messageId: string,
51
- ): MessageFeedback | undefined {
52
- return ratings.get(messageKey(agentId, threadId, messageId));
53
- }
54
-
55
- /** Calls `listener` whenever a rating in this Thread changes. */
56
- export function subscribeToMessageFeedback(
57
- agentId: string,
58
- threadId: string,
59
- listener: () => void,
60
- ): () => void {
61
- const key = threadKey(agentId, threadId);
62
- let set = listeners.get(key);
63
- if (set === undefined) {
64
- set = new Set();
65
- listeners.set(key, set);
66
- }
67
- set.add(listener);
68
- return () => {
69
- set.delete(listener);
70
- if (set.size === 0) listeners.delete(key);
71
- };
72
- }
73
-
74
- /**
75
- * Sets (or, with `null`, clears) this page's rating of one message and sends
76
- * it to the active Trajectory. Every change is a new event; the newest event
77
- * for a `feedbackId` is the current one. Like any Trajectory event, a change
78
- * made while no Trajectory captures, for example during a reconnect, is not
79
- * sent; the page still shows it.
80
- */
81
- export function setMessageFeedback(params: {
82
- copilotkit: CopilotKitCore;
83
- agentId: string;
84
- threadId: string;
85
- message: AssistantMessage;
86
- feedback: MessageFeedback | null;
87
- }): void {
88
- const { copilotkit, agentId, threadId, message } = params;
89
- const comment = normalizeComment(params.feedback?.comment);
90
- const feedback: MessageFeedback | null = params.feedback && {
91
- rating: params.feedback.rating,
92
- ...(comment ? { comment } : {}),
93
- };
94
-
95
- const key = messageKey(agentId, threadId, message.id);
96
- if (feedback) ratings.set(key, feedback);
97
- else ratings.delete(key);
98
- for (const listener of listeners.get(threadKey(agentId, threadId)) ?? [])
99
- listener();
100
-
101
- const runId = copilotkit.getRunIdForMessage(agentId, threadId, message.id);
102
- copilotkit.emitTrajectoryEvent(MESSAGE_FEEDBACK_EVENT_NAME, {
103
- feedbackId: `${threadId}:${message.id}`,
104
- threadId,
105
- messageId: message.id,
106
- agentId,
107
- ...(runId ? { runId } : {}),
108
- rating: feedback?.rating ?? null,
109
- ...(feedback?.comment ? { comment: feedback.comment } : {}),
110
- // A custom sink set to omit agent text gets no excerpt of the reply.
111
- ...(copilotkit.ɵlearningCapturesAgentText
112
- ? { response: clip(message.content ?? "", RESPONSE_EXCERPT_LENGTH) }
113
- : {}),
114
- });
115
- }
116
-
117
- /** @internal Forgets every rating, so each test starts empty. */
118
- export function ɵresetMessageFeedbackCache(): void {
119
- ratings.clear();
120
- }